From 92db39741de4eca18a6e98fe52995b9eaf7ae5a6 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 1 Feb 2023 17:00:38 +0000 Subject: [PATCH 1/7] chore(deps): update dependency google-cloud-dialogflow-cx to v1.18.0 (#499) --- 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 390c05c3..7a2cd9f3 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,3 +1,3 @@ -google-cloud-dialogflow-cx==1.17.1 +google-cloud-dialogflow-cx==1.18.0 Flask==2.2.2 python-dateutil==2.8.2 From dc50fd3b78d77d29745de7a826b392f68d4b087b Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 07:07:48 -0500 Subject: [PATCH 2/7] chore: Update gapic-generator-python to v1.8.4 (#500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Update gapic-generator-python to v1.8.4 PiperOrigin-RevId: 507808936 Source-Link: https://github.com/googleapis/googleapis/commit/64cf8492b21778ce62c66ecee81b468a293bfd4c Source-Link: https://github.com/googleapis/googleapis-gen/commit/53c48cac153d3b37f3d2c2dec4830cfd91ec4153 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNTNjNDhjYWMxNTNkM2IzN2YzZDJjMmRlYzQ4MzBjZmQ5MWVjNDE1MyJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../snippet_metadata_google.cloud.dialogflow.cx.v3.json | 2 +- .../snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json | 2 +- setup.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json index ac7adaf3..a81f2e6e 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-dialogflow-cx", - "version": "1.18.0" + "version": "0.1.0" }, "snippets": [ { diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json index d0ead9ce..1df013e0 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-dialogflow-cx", - "version": "1.18.0" + "version": "0.1.0" }, "snippets": [ { diff --git a/setup.py b/setup.py index c26496f6..1aec33f1 100644 --- a/setup.py +++ b/setup.py @@ -57,9 +57,7 @@ if package.startswith("google") ] -namespaces = ["google"] -if "google.cloud" in packages: - namespaces.append("google.cloud") +namespaces = ["google", "google.cloud"] setuptools.setup( name=name, From 9d820ff0b1e12bf067b954cdf45b0ea7cfea0942 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 15:16:24 +0000 Subject: [PATCH 3/7] build(deps): bump cryptography from 38.0.3 to 39.0.1 in /synthtool/gcp/templates/python_library/.kokoro (#501) Source-Link: https://togithub.com/googleapis/synthtool/commit/bb171351c3946d3c3c32e60f5f18cee8c464ec51 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf --- .github/.OwlBot.lock.yaml | 2 +- .kokoro/requirements.txt | 49 ++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index f0f3b24b..894fb6bc 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:f946c75373c2b0040e8e318c5e85d0cf46bc6e61d0a01f3ef94d8de974ac6790 + digest: sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index 05dc4672..096e4800 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -113,33 +113,28 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==38.0.3 \ - --hash=sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d \ - --hash=sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd \ - --hash=sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146 \ - --hash=sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7 \ - --hash=sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436 \ - --hash=sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0 \ - --hash=sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828 \ - --hash=sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b \ - --hash=sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55 \ - --hash=sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36 \ - --hash=sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50 \ - --hash=sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2 \ - --hash=sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a \ - --hash=sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8 \ - --hash=sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0 \ - --hash=sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548 \ - --hash=sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320 \ - --hash=sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748 \ - --hash=sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249 \ - --hash=sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959 \ - --hash=sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f \ - --hash=sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0 \ - --hash=sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd \ - --hash=sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220 \ - --hash=sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c \ - --hash=sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722 +cryptography==39.0.1 \ + --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ + --hash=sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f \ + --hash=sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502 \ + --hash=sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41 \ + --hash=sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965 \ + --hash=sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e \ + --hash=sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc \ + --hash=sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad \ + --hash=sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505 \ + --hash=sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388 \ + --hash=sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6 \ + --hash=sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2 \ + --hash=sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac \ + --hash=sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695 \ + --hash=sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6 \ + --hash=sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336 \ + --hash=sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0 \ + --hash=sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c \ + --hash=sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106 \ + --hash=sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a \ + --hash=sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8 # via # gcp-releasetool # secretstorage From f2d12c53804dec7b236509aa29b200aebcc53c8a Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 16 Feb 2023 11:57:20 +0000 Subject: [PATCH 4/7] chore(deps): update dependency flask to v2.2.3 (#503) --- 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 7a2cd9f3..ba3cb8c1 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,3 +1,3 @@ google-cloud-dialogflow-cx==1.18.0 -Flask==2.2.2 +Flask==2.2.3 python-dateutil==2.8.2 From 199a9e6e59ea18d9b1901907057030831511fc78 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:32:11 +0000 Subject: [PATCH 5/7] chore(python): upgrade gcp-releasetool in .kokoro [autoapprove] (#504) Source-Link: https://togithub.com/googleapis/synthtool/commit/5f2a6089f73abf06238fe4310f6a14d6f6d1eed3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:8555f0e37e6261408f792bfd6635102d2da5ad73f8f09bcb24f25e6afb5fac97 --- .github/.OwlBot.lock.yaml | 2 +- .kokoro/requirements.in | 2 +- .kokoro/requirements.txt | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 894fb6bc..5fc5daa3 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf + digest: sha256:8555f0e37e6261408f792bfd6635102d2da5ad73f8f09bcb24f25e6afb5fac97 diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in index cbd7e77f..882178ce 100644 --- a/.kokoro/requirements.in +++ b/.kokoro/requirements.in @@ -1,5 +1,5 @@ gcp-docuploader -gcp-releasetool +gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x importlib-metadata typing-extensions twine diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index 096e4800..fa99c129 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -154,9 +154,9 @@ gcp-docuploader==0.6.4 \ --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf # via -r requirements.in -gcp-releasetool==1.10.0 \ - --hash=sha256:72a38ca91b59c24f7e699e9227c90cbe4dd71b789383cb0164b088abae294c83 \ - --hash=sha256:8c7c99320208383d4bb2b808c6880eb7a81424afe7cdba3c8d84b25f4f0e097d +gcp-releasetool==1.10.5 \ + --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ + --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 # via -r requirements.in google-api-core==2.10.2 \ --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ From acfd1a11aca9c9cba7fb351f1781fa73c1e1d985 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 04:12:28 -0800 Subject: [PATCH 6/7] feat: Added persist_parameter_changes field from `query_params` to MatchIntentRequest (#502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Added persist_parameter_changes field from `query_params` to MatchIntentRequest PiperOrigin-RevId: 512734924 Source-Link: https://github.com/googleapis/googleapis/commit/7483cdc69f29271e75fc046d5e5648b2fe4f1ee9 Source-Link: https://github.com/googleapis/googleapis-gen/commit/86f1d59a749e8075b345ce15730019dd1528f288 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiODZmMWQ1OWE3NDllODA3NWIzNDVjZTE1NzMwMDE5ZGQxNTI4ZjI4OCJ9 feat: Support REST transport PiperOrigin-RevId: 511900156 Source-Link: https://github.com/googleapis/googleapis/commit/8310e03b95b830009f55c2632d10e1ade4a2d942 Source-Link: https://github.com/googleapis/googleapis-gen/commit/13b8eb16bdb98a9a71ea421710dd17c438343300 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMTNiOGViMTZiZGI5OGE5YTcxZWE0MjE3MTBkZDE3YzQzODM0MzMwMCJ9 chore: Update gapic-generator-python to v1.8.5 PiperOrigin-RevId: 511892190 Source-Link: https://github.com/googleapis/googleapis/commit/a45d9c09c1287ffdf938f4e8083e791046c0b23b Source-Link: https://github.com/googleapis/googleapis-gen/commit/1907294b1d8365ea24f8c5f2e059a64124c4ed3b Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMTkwNzI5NGIxZDgzNjVlYTI0ZjhjNWYyZTA1OWE2NDEyNGM0ZWQzYiJ9 feat: added gcs.proto. added support for GcsDestination and TextToSpeechSettings feat!: Remove [REQUIRED] for VersionConfig docs: add more meaningful comments PiperOrigin-RevId: 511306725 Source-Link: https://github.com/googleapis/googleapis/commit/57673deca3ffe1b4f5f42549de19698fc73c6c6f Source-Link: https://github.com/googleapis/googleapis-gen/commit/9adddda268e1b131d6dfcb934f8b34f78489bf08 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiOWFkZGRkYTI2OGUxYjEzMWQ2ZGZjYjkzNGY4YjM0Zjc4NDg5YmYwOCJ9 feat: added gcs.proto. added support for GcsDestination and TextToSpeechSettings. feat!: Remove [REQUIRED] for VersionConfig docs: add more meaningful comments PiperOrigin-RevId: 510257108 Source-Link: https://github.com/googleapis/googleapis/commit/140334ba7124add3d6a6068db353b1578b43c9c9 Source-Link: https://github.com/googleapis/googleapis-gen/commit/88ee5015667d71642e8e9cf9c6e605a382ee394c Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiODhlZTUwMTU2NjdkNzE2NDJlOGU5Y2Y5YzZlNjA1YTM4MmVlMzk0YyJ9 feat: enable "rest" transport in Python for services supporting numeric enums PiperOrigin-RevId: 508143576 Source-Link: https://github.com/googleapis/googleapis/commit/7a702a989db3b413f39ff8994ca53fb38b6928c2 Source-Link: https://github.com/googleapis/googleapis-gen/commit/6ad1279c0e7aa787ac6b66c9fd4a210692edffcd Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNmFkMTI3OWMwZTdhYTc4N2FjNmI2NmM5ZmQ0YTIxMDY5MmVkZmZjZCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- dialogflow-cx-v3-py.tar.gz | Bin 491520 -> 737280 bytes dialogflow-cx-v3beta1-py.tar.gz | Bin 491520 -> 671744 bytes google/cloud/dialogflowcx/__init__.py | 4 + google/cloud/dialogflowcx_v3/__init__.py | 4 + .../cloud/dialogflowcx_v3/gapic_metadata.json | 583 ++- .../services/agents/async_client.py | 1 + .../dialogflowcx_v3/services/agents/client.py | 3 + .../services/agents/transports/__init__.py | 5 + .../services/agents/transports/rest.py | 1890 +++++++ .../services/changelogs/client.py | 2 + .../changelogs/transports/__init__.py | 5 + .../services/changelogs/transports/rest.py | 895 ++++ .../services/deployments/client.py | 2 + .../deployments/transports/__init__.py | 5 + .../services/deployments/transports/rest.py | 906 ++++ .../services/entity_types/client.py | 2 + .../entity_types/transports/__init__.py | 5 + .../services/entity_types/transports/rest.py | 1388 +++++ .../services/environments/async_client.py | 2 - .../services/environments/client.py | 4 +- .../environments/transports/__init__.py | 5 + .../services/environments/transports/rest.py | 1909 +++++++ .../services/experiments/client.py | 2 + .../experiments/transports/__init__.py | 5 + .../services/experiments/transports/rest.py | 1569 ++++++ .../dialogflowcx_v3/services/flows/client.py | 2 + .../services/flows/transports/__init__.py | 5 + .../services/flows/transports/rest.py | 2043 ++++++++ .../services/intents/client.py | 2 + .../services/intents/transports/__init__.py | 5 + .../services/intents/transports/rest.py | 1274 +++++ .../dialogflowcx_v3/services/pages/client.py | 2 + .../services/pages/transports/__init__.py | 5 + .../services/pages/transports/rest.py | 1305 +++++ .../security_settings_service/client.py | 2 + .../transports/__init__.py | 5 + .../transports/rest.py | 1327 +++++ .../services/session_entity_types/client.py | 2 + .../transports/__init__.py | 5 + .../session_entity_types/transports/rest.py | 1390 +++++ .../services/sessions/client.py | 2 + .../services/sessions/transports/__init__.py | 5 + .../services/sessions/transports/rest.py | 1069 ++++ .../services/test_cases/client.py | 2 + .../test_cases/transports/__init__.py | 5 + .../services/test_cases/transports/rest.py | 2295 +++++++++ .../transition_route_groups/client.py | 2 + .../transports/__init__.py | 5 + .../transports/rest.py | 1343 +++++ .../services/versions/client.py | 2 + .../services/versions/transports/__init__.py | 5 + .../services/versions/transports/rest.py | 1598 ++++++ .../services/webhooks/client.py | 2 + .../services/webhooks/transports/__init__.py | 5 + .../services/webhooks/transports/rest.py | 1280 +++++ .../cloud/dialogflowcx_v3/types/__init__.py | 6 + .../types/advanced_settings.py | 16 + google/cloud/dialogflowcx_v3/types/agent.py | 12 + .../dialogflowcx_v3/types/audio_config.py | 24 + .../cloud/dialogflowcx_v3/types/changelog.py | 2 + .../cloud/dialogflowcx_v3/types/deployment.py | 2 + .../dialogflowcx_v3/types/entity_type.py | 2 + .../dialogflowcx_v3/types/environment.py | 10 +- .../cloud/dialogflowcx_v3/types/experiment.py | 2 + google/cloud/dialogflowcx_v3/types/flow.py | 2 + .../dialogflowcx_v3/types/fulfillment.py | 2 + google/cloud/dialogflowcx_v3/types/gcs.py | 51 + google/cloud/dialogflowcx_v3/types/intent.py | 2 + google/cloud/dialogflowcx_v3/types/page.py | 2 + .../dialogflowcx_v3/types/response_message.py | 2 + .../types/security_settings.py | 2 + google/cloud/dialogflowcx_v3/types/session.py | 8 + .../types/session_entity_type.py | 2 + .../cloud/dialogflowcx_v3/types/test_case.py | 2 + .../types/transition_route_group.py | 2 + .../types/validation_message.py | 2 + google/cloud/dialogflowcx_v3/types/version.py | 2 + google/cloud/dialogflowcx_v3/types/webhook.py | 2 + google/cloud/dialogflowcx_v3beta1/__init__.py | 4 + .../dialogflowcx_v3beta1/gapic_metadata.json | 583 ++- .../services/agents/async_client.py | 1 + .../services/agents/client.py | 3 + .../services/agents/transports/__init__.py | 5 + .../services/agents/transports/rest.py | 1894 +++++++ .../services/changelogs/client.py | 2 + .../changelogs/transports/__init__.py | 5 + .../services/changelogs/transports/rest.py | 895 ++++ .../services/deployments/client.py | 2 + .../deployments/transports/__init__.py | 5 + .../services/deployments/transports/rest.py | 906 ++++ .../services/entity_types/client.py | 2 + .../entity_types/transports/__init__.py | 5 + .../services/entity_types/transports/rest.py | 1388 +++++ .../services/environments/async_client.py | 2 - .../services/environments/client.py | 4 +- .../environments/transports/__init__.py | 5 + .../services/environments/transports/rest.py | 1909 +++++++ .../services/experiments/client.py | 2 + .../experiments/transports/__init__.py | 5 + .../services/experiments/transports/rest.py | 1569 ++++++ .../services/flows/client.py | 2 + .../services/flows/transports/__init__.py | 5 + .../services/flows/transports/rest.py | 2044 ++++++++ .../services/intents/client.py | 2 + .../services/intents/transports/__init__.py | 5 + .../services/intents/transports/rest.py | 1274 +++++ .../services/pages/client.py | 2 + .../services/pages/transports/__init__.py | 5 + .../services/pages/transports/rest.py | 1306 +++++ .../security_settings_service/client.py | 2 + .../transports/__init__.py | 5 + .../transports/rest.py | 1327 +++++ .../services/session_entity_types/client.py | 2 + .../transports/__init__.py | 5 + .../session_entity_types/transports/rest.py | 1390 +++++ .../services/sessions/client.py | 2 + .../services/sessions/transports/__init__.py | 5 + .../services/sessions/transports/rest.py | 1069 ++++ .../services/test_cases/client.py | 2 + .../test_cases/transports/__init__.py | 5 + .../services/test_cases/transports/rest.py | 2295 +++++++++ .../transition_route_groups/client.py | 2 + .../transports/__init__.py | 5 + .../transports/rest.py | 1343 +++++ .../services/versions/client.py | 2 + .../services/versions/transports/__init__.py | 5 + .../services/versions/transports/rest.py | 1598 ++++++ .../services/webhooks/client.py | 2 + .../services/webhooks/transports/__init__.py | 5 + .../services/webhooks/transports/rest.py | 1280 +++++ .../dialogflowcx_v3beta1/types/__init__.py | 6 + .../types/advanced_settings.py | 16 + .../cloud/dialogflowcx_v3beta1/types/agent.py | 12 + .../types/audio_config.py | 24 + .../dialogflowcx_v3beta1/types/changelog.py | 2 + .../dialogflowcx_v3beta1/types/deployment.py | 2 + .../dialogflowcx_v3beta1/types/entity_type.py | 2 + .../dialogflowcx_v3beta1/types/environment.py | 10 +- .../dialogflowcx_v3beta1/types/experiment.py | 2 + .../cloud/dialogflowcx_v3beta1/types/flow.py | 2 + .../dialogflowcx_v3beta1/types/fulfillment.py | 2 + .../cloud/dialogflowcx_v3beta1/types/gcs.py | 51 + .../dialogflowcx_v3beta1/types/intent.py | 2 + .../cloud/dialogflowcx_v3beta1/types/page.py | 2 + .../types/response_message.py | 2 + .../types/security_settings.py | 2 + .../dialogflowcx_v3beta1/types/session.py | 2 + .../types/session_entity_type.py | 2 + .../dialogflowcx_v3beta1/types/test_case.py | 2 + .../types/transition_route_group.py | 2 + .../types/validation_message.py | 2 + .../dialogflowcx_v3beta1/types/version.py | 2 + .../dialogflowcx_v3beta1/types/webhook.py | 2 + ...d_environments_create_environment_async.py | 1 - ...ed_environments_create_environment_sync.py | 1 - ...d_environments_update_environment_async.py | 1 - ...ed_environments_update_environment_sync.py | 1 - ...d_environments_create_environment_async.py | 1 - ...ed_environments_create_environment_sync.py | 1 - ...d_environments_update_environment_async.py | 1 - ...ed_environments_update_environment_sync.py | 1 - ...etadata_google.cloud.dialogflow.cx.v3.json | 56 +- ...ta_google.cloud.dialogflow.cx.v3beta1.json | 56 +- scripts/fixup_dialogflowcx_v3_keywords.py | 2 +- .../unit/gapic/dialogflowcx_v3/test_agents.py | 3250 +++++++++++- .../gapic/dialogflowcx_v3/test_changelogs.py | 987 +++- .../gapic/dialogflowcx_v3/test_deployments.py | 995 +++- .../dialogflowcx_v3/test_entity_types.py | 2232 +++++++- .../dialogflowcx_v3/test_environments.py | 3295 +++++++++++- .../gapic/dialogflowcx_v3/test_experiments.py | 2994 ++++++++++- .../unit/gapic/dialogflowcx_v3/test_flows.py | 3694 ++++++++++++- .../gapic/dialogflowcx_v3/test_intents.py | 2089 +++++++- .../unit/gapic/dialogflowcx_v3/test_pages.py | 2461 ++++++++- .../test_security_settings_service.py | 2168 +++++++- .../test_session_entity_types.py | 2250 +++++++- .../gapic/dialogflowcx_v3/test_sessions.py | 974 +++- .../gapic/dialogflowcx_v3/test_test_cases.py | 4566 ++++++++++++++++- .../test_transition_route_groups.py | 2555 ++++++++- .../gapic/dialogflowcx_v3/test_versions.py | 2603 +++++++++- .../gapic/dialogflowcx_v3/test_webhooks.py | 2085 +++++++- .../gapic/dialogflowcx_v3beta1/test_agents.py | 3254 +++++++++++- .../dialogflowcx_v3beta1/test_changelogs.py | 987 +++- .../dialogflowcx_v3beta1/test_deployments.py | 995 +++- .../dialogflowcx_v3beta1/test_entity_types.py | 2232 +++++++- .../dialogflowcx_v3beta1/test_environments.py | 3295 +++++++++++- .../dialogflowcx_v3beta1/test_experiments.py | 2994 ++++++++++- .../gapic/dialogflowcx_v3beta1/test_flows.py | 3694 ++++++++++++- .../dialogflowcx_v3beta1/test_intents.py | 2089 +++++++- .../gapic/dialogflowcx_v3beta1/test_pages.py | 2461 ++++++++- .../test_security_settings_service.py | 2168 +++++++- .../test_session_entity_types.py | 2250 +++++++- .../dialogflowcx_v3beta1/test_sessions.py | 974 +++- .../dialogflowcx_v3beta1/test_test_cases.py | 4566 ++++++++++++++++- .../test_transition_route_groups.py | 2555 ++++++++- .../dialogflowcx_v3beta1/test_versions.py | 2603 +++++++++- .../dialogflowcx_v3beta1/test_webhooks.py | 2085 +++++++- 196 files changed, 123659 insertions(+), 3557 deletions(-) create mode 100644 google/cloud/dialogflowcx_v3/services/agents/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/changelogs/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/deployments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/entity_types/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/environments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/experiments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/flows/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/intents/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/pages/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/security_settings_service/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/session_entity_types/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/sessions/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/test_cases/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/versions/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/services/webhooks/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3/types/gcs.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/agents/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/deployments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/environments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/experiments/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/flows/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/intents/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/pages/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/sessions/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/versions/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/rest.py create mode 100644 google/cloud/dialogflowcx_v3beta1/types/gcs.py diff --git a/dialogflow-cx-v3-py.tar.gz b/dialogflow-cx-v3-py.tar.gz index 7eef5dbd4613f6861ee130efb3f0d86b84e21451..843316ab347b1cffb82fe672402a5801622872f1 100644 GIT binary patch literal 737280 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

GWe-(c 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%0A<-Jm_+nGtp=D!yj-%dloFoF6G1~^8@sA{pG|b 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

|E1|?N+-<&=zP4e+yPCp?+YKfaa3t-4W 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

@R69{MnI%5z<2+8qwRGQ{ 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 z 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�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 )A12LxnUE}^&&${M+)eg?Oc`g9U@ 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�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

>1(_wwMZM+xJPDR^0@+= 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

_omQ?9oOSz{FIye#bLxWBqNwIgy7pm&Bs5gNO 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∓-D}xlGb2^m_M3b;?L)rqG!{I>b(W=wvHc% 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�?pniGIdMt8KOQ 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

yD5wJy)B2R*X9J! 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%0AQW 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

7>d|YQ!qT^A!v1&^pMX4pOP2M_Q@M4}lUq{SUi) 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

G0J1?bC&q+{UZf6fSy8V0778Z2?h^ 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⁣5uw*G0d7$e^NIa*4?K>(dE5FisF1~(BF7-u{^to+P=MF@qjsMD@696p;bhCp$u`h2(G3Z{R5sbjG3WhQBDP9Eb}O`(TfJB!`mO9aOO#%p@=4L$(@ zEpdaC$?7t|kl3}wQe+8>88P-&8iA9pLn zsl^diD-32uSNA2_}A98VzaTAVyrU_Loh*u#`cJ zY>}p1MK2Z9>|zAg9-JvwnL#8C75voh$6A}~Py}xOvDPp$(RmZq&fmx-;Ftpz!{u<1 zDj6>q!>pY*&C=5R=nl0fNMULrc1ARV$rN2@Q0Z-8;%j29e~N;I?qRc55ZlG+veKLq z78!9(3;b+e-dDfY&xP3-_#Wx~ORQ$KjEK%t&4(!H%XPx~sk$QzG7pZ@blY`f0Wzhh&?ZPTDVU{J;{am_$siE%$xfMG?cZ5a77F6DwQ42(wgz>*fD$Geo` z2obaF7voy*GBT|+T*x%aVHMLZLa|WBl%0x8D~sp5rwYPl1a#S+i0jkbdUodq~Q1q}8K8 z2t7R2n;N)d5v{u_Dbf_#Z;Fb_M;ZZ97|NO;GL+#M){tr9l;Cj=%H+>+8Tm87wQA0* z65-8pdkoEqp{_)ui~2SA4uf;>OS0i(9w~5zV_YB{!ImUMDS+DENtUo|*#3^%hgGy2 z#~HdZAK6R4q@RItGeLf2-dF=~mwP~APIf$PrSbMe+we)}WIr`@q1n@XhT(0#k}zl| zJJqd%z|&^qPj8YQJ45e)$Yl-#oYElPVv=Fh=Ll3|A5DwPP|LvwFxneVhi*BRZuh63 zT-De)d3B{%PvCbZYb%y`N+`{CrcFh>g+#b5~$31H~boXgXKj^zib@mtWWIt)i z;3j3+&&GsNELAmZ4Ij$T(edUQ(J;^KsUhS_^p188!eyh=c}m}Yqd{a4cDKA2Q40%# zUYWSBQ%V`1ZKm&w{qb_I@}isJ{0_T|+Sn_Lfd#v$^pKRw(`)#cddEfL#T_kl(_)i*wAymElnghSJDk z4BW2^p66?Ov<54bpzG+0w8Q`^3tn_ZSV%&fB)l0P$uejm=bJMhdqWT4&z!`q9W~leCADos%b3JmO9LM~3}e}rvB_!{6mjFV)AXB% zk5h6{NMX3Om|>FEWL9oQDLdYJ(DTz%#&>(g@xY@OWUOyEqrQ6#ygQSZ9$&Hv?V*Q#@Q7w$NZ(s&!nv8 zkw*B54dJqxwYfH51759}gb_8jPp6?dutFB_>s3g4VWyD?9hlO{!(EY?3eo`mJO1!R zXf+Qs2LkMxX$yKK$e}D}9Mm;-iRk|HPv{WX(mwQP!Kn@KRzF z;JnBxha@lNX*jw$mlscf1V5?Tvly#F=Sh17A@ z&B$aiaEqz{Lr!rehs^oe_zfdme2TB>+QcsTW(Q3uJo2Y+(dDy(%2431a@)P1GeHw< zFd^zNq&$1Yt6|F8pR~UKHe7PEyyE;$VzR~dv58ZP-D2TdJHS`wj$PdpKlGXtUp{}6 zbK1;}oXA^y$({M{2agZ_omW=jvozRm@BeO+=b5t9X zOG=oV=b__!8=*DS-9I9{-o`f>#26vg>os!oBTO`>>ZR|k6b5Cc(Dzp9q@)${$$@hVztbu=jVa%5C!#d~~@T_yEU z)ak%Ua*owN!^81=i8cbEjuO7gHr0EKQ@^44j_$wH@PJ&PMT~86Z|R9#kEpJ#ZvGSF z(jcioG6}55{SjZBRPHd7s807)cfB#pxXZ4U8g0zD8TN-w=9#fzNqRr=&l#gkkLr|y zn-OL9;(YNM|Js&s&wD0ZqzB-g5PR~_H89s{6lA|>ivdYy1xy(Y`aYJ^-Ol72x_2>u zW2ze*trY3kJlS)>7z6&LUpVh;o)mPnYNqv~UKN)2aLIs-&UO#4&$mY#P6%e&y5OW; zHR0L^Em##%=GqECfHW&(VL^=)xjn>Kt{#@xaH5`RH#Cr00|E~sMX3>qU7d<9ddE#& z*E_C-xonj3>U#E{s@vo=`f%#DY3~t9piApN7ebaEH3!}vm_r?uE*G|{ItFl_QR*B! ztyPQ`1d|yYLyv#Dix^X;+LDVei5fPvv3waGdMB@^22%et+5AEMZH6JC?6YHBZHV`` zT8ip$sfCuf9N#41c$<^sy$bpf(Z>>taf1Pj0ezO`auC_5>2k2b$ipptH6ZL$QafzJ zZ4tw77IolDqUsRm^Lj#4n-TqhAEdbQ5(MWidjhp$WXhegYE#7xK)n?PvbFnHtp^1R z&j+ca6Lag}Uqg<)DU5DHGS3u0soI8K8vA{8RPMvR?J7B_J}$RM$h@JK*IS_PwSu3! zBc3qSZL1Yvh1rK&T#;2sBiDxta1|Sm5O&inWD@TVQmFg#Ag(m$aAr;HX}f zBf6tX?fPpB@7`#>KBzv<`*yd53Tm|OQ<5Ckww2aN@x?N9B$rmGB15XL!vz%=gApOm zPW>cE=cx83nHqsN3M-fIG-A-V8^MarpHQyiBaunj!X%pkJC;7(P!LU zzB->wyxE}_6AK&BOAAze6Tv)MszC@T9YIr=)n`&X&^=hf3=5#_^xXwE|G3Fp-!Wbd z%`BqHb?sVS`KNJx(H`uQU$#v%GqxM6E=Dq|Z*bXxCTaa%G(17Io{7@X8qyEcG-Zy% zu+KAOmmd|zkT(7H0<%!vAw+ATk@sm$4KJ=kChg-9rw&TGGv@LSZ~%JJKs$u|sPiLU zKQxnQZh(me7nUYjznnpMqv;$amN}2ch0xt+YoM@x6G_-4;OJg@T|OHf?n+Ru?!*gz zvI^MOlGwiTZ&>r#iH)vq0so+VEH+B^HHn?31;7)g*1|sbVrQ$keM7|gzSiTT(Xn*- z8JZK-?6BV4b~xv8^tUKbk7+ND;UDCJMgS~N%$H5vCY>4^mm}qyV zRxm#aH6tHPq~kno68=Od`U8qsv@Nw`iNX#hY~LJ}j+YP7N@~#5HcW^-?bNK=Sj80} zMq8b=b6bDo>ii}gvk$s;RMX>~2eIxl#QnT?=4nZrbX$~nYMwrZucL99U^~+y}Q}k(u1BqR_L~J#7p_qiQVso zD%mZlZ1!E_4wYy~4IT#7-OYs74XOV=&4F|m?@E?TP!<-$dneWh)~w9Fr{{c}j5_1*mEkid_g2xdI>JukhT>P9ssDB|lT8AFX3!$A5)xr2Bblu%nhcW|KgyWM6scbnaD8Nh&b+p zh-bWwSM!+8J*hSMgjm#DgtvXQ3&0U0Drua&JB}ZOQ7e74j5CcN%P({+!IZpK+OrRj zcmBL7C#^(>Q*p_OcF(IsL0FAMvPwegH6@C|WCOsR7lmP$(SJVD{S})UDpoi37HN{K z0j1myt@NhK_%{x$$nl&;EtYq>JBTHO{tXe7u2D6w&Q`tRqCfEmm;s`1o~|^YTu~+H z@j1e2FLL6@FD~$SGZ1W~hV&tXe1qi*%j>uLP-rbpp~9rn%R~Ze*r_!X5$?J&^Axu= zVXvH!=UgtYNN%3_71BFY#xi-&4lm?@u-Pb?NV-y#PPQ>UTx4W1>MOQP9?=H3#U-TuwG^$lgb2hFP_d4kt8`GDoGc{Lg!d2;;$RUIw_2$4m=E3MYd zUf)^raCulfY2{z=Z0&$oZR%{6ntOlm`--UO1SuTcfT+X(pFp|cunB74!||@zw0^E? zt*@omgocVLi*)n<<^9flIj&6q<^4D~qjGZIet5sa^{+oaydSL_OV%*WjC>p54mPjU z>q)xy;{BW%yRdhA^Sb>s$M-Eh#sTo?2lD_`*Y)?4ac)``t@AGL?6lCwekcM(>xfVk@#l*>K-KG^CC0I?(r$&RfU4C7{Oo5QyAHf>{69t++3op z`Dgbq0h4S*Ylc5_F&|)xy|dfV_0+Q$0C+pShgN`s&|-wMj|#u%iUtQT9%Q&13(eE= zDx~z}z07Y>=gUQOlDvdlKkGyDET4_R4ex|O^B@%7K%{h#N$zKVaEX!f zP4}>=NjVE1YY=zZyQ`^RF}j*YODsYDDHFaWp!pW(m|#mnS5&Y$CLoSDtio+YrB2cG zbp6bGwt^0Nm-~j^-VS!*VN6(UU-AS!(&bF{Mw1C?g%xbv#sjqZ)Hl|hcSPLx}qkN=r*gqswT|5IQ&sX`rzQh0CiFh<)D!*ELQb5vgD zO!PmG&y_sF_sJ;pOhE4FhFD)Zb@!SG)VKpe^uiR3;U>3o6o1*!i>*kFhS^IuqCgJ zwnGwQvF(;$l5`?g;*f6fD5F=}hZSVsRo_CADhjxaqx%~-*YmFV0%<$9PXGrN7CvkM zj|CrZ*xZNvVa`8KBFOO*iU?S+6~_M@M1gS+10;~bd+r%t<_s}nUCG)h$6_)MOOqCA z*({E38F_HL-|oLYU*C70VG^e}?QYpD`7Nwqf~uGWFm9RKqy!zjtJ7rG{wL{^N6HfeEv3yoauqN~({?Oksp_tt)4Vv8Bq?FvaXK|#^h&hilUA$5;yM?17O zmIt?!5|; z`VeVDsh=&pos^iMS1o&%JpJLfF%B_&-zuxTYaox3=*XQW9p8(*2*Mx>o5@6A28Wf? zn5PwCX_g7bO-tfH$B-4t)!V{R8WqThA6EZ@N$%48YlT9DeJJ!h=9)$|90P;$#SM~T z^xo7KWj4C)4hH@&Y33e7f5YB`jt@vfs<*cvbR}@?D0_AMfeiIIS+Z~&P>lppk}ywd z6@~K0O^?2ogAML}jgF6--x$O%|HG~QL}R?H+3^*xmXU<=W7sC-O=62Ksoe}jRMBAu zKw787y(`g?bCbVy7$}ORuUBeuBav|NuV#1z0vf#3^}@srCydvybc$;t3oNQ^aY0B; zxYC7clf)Fu14wnw-d8`|TxspXuSa(0Kt4ny*8LfC^DP|D(bLWQ89#zhM{5TQexW0r zNOiXS!MV8^?Y1;Hz#Dcv7f_x-tL+K-wW)nNu znq{`((X@Pegit!`eBQH@ue zvtzU=*!T8f!oF7EfXP2YWETVxKpQJBnQ(|%dPerb8zFS%|xhZ`eUg*W6ag^h>HOH30jFgx+Wkt{qN?#hrRNR2MT zk}V*3=*W;$p#shs%&lHRrV3@b)QU;w5B;nsb0B|^xcD7HL)@!;lVM!d^*7(9qs_qm zq9HpOAG(PrVF*DTd+%!(Jg)Q)H1TBgB<2^pH|#wZW5K{QFBquk;_GA;2yybW3Z*8p z6x1ixGbN&{KBTx}0u!j*BEpUUG8|mKDy|&4gU~~$fYqyp0P;EMDuz%VZJaQ&HL_f3 z)7OV0Q`e0SH2GqTL43uPZ+|95OnZIFGx|Cp=^{o7b6;RE{91Pa#Fcug7F2`n5Nbb) z1DTPrl*wK=jk?Yj6SUZ}m zhk$&E^(9TbXKD;w9SNiS1%gILw`jfaVZDpk^{=+S*+|R#jnvW>aJK4-dN`9X@jt#C zoSee$b*!w2OWcny2T|l8uMrVQX|twbKU5Iij(YA`DO*xhLob9w9DiLbycHzcPlaSOCPO$_0opJ=q8oV;`iWRd& z`cQZyQLv2-vtf>4#}d8Cb%w%p@+?w#J)8RDV6!Ze$drW~(n~R+xxk*TSWxQLt@{ex zxrnVz9Q2KVjXLaz?}Z)e(5!onJ03T_N6)sk9zwop%b9amYzE0@RI*Sef@l}aO>e78#ev~J~u0sdclv4~Y)gKWuQ)HRdnnt{!Qeh4;QY5(^ zHP6l!JPKz!g^WSK%Sn5<#g986@ zOZO>{vbJ3PNtxUE1zM{~OWNy8h`WMnwreDB`nyIVCHDMMfSo>Z8^8(>o+MrhC{n-7 zkV6#!WzOpAmu%T&OAJ1x;cyQG7b#Q|{z!UfqHxh>_@gyy!V=}1%79UrVGYpFn~*7b z>rN53+VjOU4C@D1JOc5`QLn~YdMK!d+S*UwAS1Biv@qzOpR04De;UlsgKk}t7toAm zzcPG65z2{_gA{1#!W22GxTI6K5C@mAipy8>DD0Tuke%5QSF{*TB~+Ke3lfTagFLr# z6mIvq5Jeg$lDwfaPpZBV$4r@vQ9fnqelyJ3clvR-gy(h=J#y3Kc8#CLLJfXBn9nr$ zRWNQ-jb5EIMS?YoXTx7#j$@V>Qp)mE6s>2=K~dd9*~cNhnT5A7=!?h)B zl1(Yk24s1lf9F<8_(dbQg9Jx~$q!?3B{b-u-NRIKy_cyVgVs|V#`7|BWM6mpWgY}w z#}Z2`4qiYN`}{XG()ycMTPhRU`uWN(+5mVZJERC;d?0zwY_`yOh3+vPN7oo*H!ADf z90EJadrTZ|otG&&CofhphkJn^l*8+jT!tK}+VO57ro6d#ZjZbCKEch@=vi$|Ruml0 zd&gd!SQx9~s30j7+p8HB4P^sAihwYZZ8@Aad&Ri_6Q<(e&sFvCMz#wrReA8DP_glE zKswUoI})3ns^{HEFOg;Rlj_MUz-dikJJ$`;brtOtz3j&GImA^HNXA83=#^Vi)@{%0 znWt>iMy&b#Bj&|{cQS-#9{scD^GO1^eu!(sV==&AB$S~F7AG~VML z^z*@P3t}>6#{JG{Y*)Lk1Y>quZ=tuD@k2B8$K?*@(5_k`dw(bK+<)eJcOBL0m|8)i zME|0F$eJ8=*#n75W2hvL0;}rllneAw%NkR!nNd5EbB^Lt+l~f=x{Lcj9WP{S{yb(h zl<5T(!OVFR88G#|hL~3NfowwL_>hP}oC5IB5F3y@;Pay!bt= z+7>KumSy~^*3JU4sMKJ0yMk&IE3Pp-&ft{!0H5ju0LVCxU?7!zR{E2Tl;{c=2A6O` zTsnbJqY|M;54aFFe@YTuGhM&6bfFV9uVtXhv?fOOXRR%Kdt;QKi5^3yie!&hlI*rk z+T?G$q=uC7o;gRR09L<6q`p*l2Q@GYFi$%asW+1(02`?~j8?tzQM=*+$&5mIiP*!> zoWs~^VTW374jdOB%6wByBXcClH>`FjW47OUDtE1CqQp+f9lJ=2-B@oLRItKQ5 z?k{=f(q!Yef1E=@00^2W3v^3YfA$iKaUIO1T#Ur-$f6(-cROled9cb|9-RfO-w)Gr za7h&i@Kz1OGVwk9-=Z%R#xWb&JG-fplbwve^M;cVKY)cj*J}vbP5?tqLOW6{FIUEhwzt zB;*Mj|4bxrP7R#xN*z6?$Lm{CYIB?1PVc358V;&lqWRuNK*+n;HH3AmjtEecD=;RU-#|fA5{mrILA`z7VUeJL) zHIAsg6|{&(=muN1@sfH^MOr@OxO9=GEdBTd{;-2&w9<<+q5B1g!U*tr@}l?244s-@ zOTwYG;R9g+5^4UqUn9CnhBat&JZqLSV%-y#BF$vT_VTJRzsdYyVX-wYY`-w5-PcH= z9gCim)Z`5IBk=@e>W7kR;@nsIgjC6;KoRP)#LmBBTTKsHZD{HCh%yG^1%#Y_i#JFJ zTP#7!l$XPX<)yyyt;e8Oi|6i$u0^FMmYI37bNl1D30gUgVa1N#NAUs?8V$*|Ed5Cq z{4+^+8s3?S(Uu5l%k8;EN9IROJhXDZ7(GT~k&$*m-*%dKyYB`FVKd_`s3Bf6DZ2leSK!0Z?x%=%-i-PWzp5sBj1T9P&-h$*d z0RoZ_xjI>w?>07f!*v)~)@+vu_H(d-V!ZqpZfe(EpMC;DVLaKfFdt}4aH9Iq^r@Xv z4}zA+-V!@xRHx!b!GM+-I6(xKk$ye1HF%B9&-+Q|9+Xm07!#8-hIv+bq>hE3kliQN z%mt!J`Db)@`c5fq`MG8O1B;hN5$GjE0?@XNAD#hsc=IcKth)eXY%@_5F#o8_ zDTbN-8__5Mp7~lDD`axN7Unh)#eEp#e5artx<`UdxD54|cyvXtXTT$wuomXHA}PLn z&qT+Cs=cjnFQDjxE>ApG;#jRJs7osPIx8YpMf;~(0N*qWr3w7`sW*2|A#HW-SYVaG zKOwu&JDZUQcOOEk4#-MHkqly3;`BT4zF!WR0AQMSVtjLzC5v5vm}}n=aE@ z>u-%<wlV7?981Mak>kBOvED<|4eul1~pWYPf8 zb3_k04Ixd%PCz{IV6G;5AuVxFWTn7toXgBrBR}w9&P?y0O*;IfxXTW)m4fYtf9#Qc z=%G!kV_%C=@#GT~pC7(Kal5Q~GsvKbmY!rl=#!$UPh|#8&CtMt9 z(Inda*~BGyVyqDp!axR;d_riTtWXhd;C)|R>{tPlHT;g08Gb7?$g?}I3yw>ZUYbCw z#efk{){fy*Qv=KyYBO0e7WIIlYj4jYf+Di5+C}uV(z+n9sTWwR+OQ^SQ2Y0;Ayd*# zE(aliA~WiLx6W~a~2>H2pz>t8epAAu*BVz3wZ zkpy7C)@SxvV63-R$XDf&`&?o0PyugPBU;AEJ8wnIPaFrdVaPHR3_svAf{V~1o(Uxu%Z1kkY)V%Frcp;1V z_)SsRfz|#ROsgqEQlpt5z7J8BXruoJM&29x39*~iIId;WY3t6|( zhA(Zjf!^CMtM`0KD|d9=jF^sl`3>=cUIhr{qop8yCVL^Az2{EAvkz}{AFCvk%_~&b zQy9Ze3z0(xw)CaD;|&S6{I#p&bDv=06WhbP_S(dIJ2UeLzkzqTF*6;Tt+im7NUb11Lk9o6d_f_4UDo)+LoTyA`Burv!9Uti>FB#!D$63=G3 zTMk9m94<%}%h+Weh=dxGnpsoahpuX1PqcaqYlFcaXpb_&_)QsFh+H^y$P$mzXiM7N zuf3b`uKQ(m9ATphCDyg84%Pr%BTa|cn~Vm3GlsNHqP3eg3WFLiX%Rd0iz_xOks-To zYR(O)D3AC{fem#(thFW90ejNe^}U=mEwI9kIx6fwIuu3RyLB`iK;k`~6ogipp=7hN z6P=G^?=nHp_m!wV565vxi1&v8-t^q(w^bvkNb$)FLuaiSOnrMpcsDRb2;8yd7Afhm zW$g%^7cQ0&J*pdxqj%vVRRMFbcexYzT z$r=H67CziKqfKG@KDFB0)IH-FG19%&Hu)5O$O#Co1v~y1^9~hUaBggf9Vaqly@T*( zbKPwkxjzt|DV!tCyPCvW&|OKu$;+*(nP&*E7pBwWBq7-Iwu`X-dK@2~cT)8qmA#V? zZO}0})StUk^@RbBD50Jx(Srm`1SoUy(}{C_i^Cg=MaXRpfpO z@Olj3)*Z48rg6f&j*47cmj5Uo)!aSR<7o+5rgWHA{WSK`UG{ZVDzxE}beb)xJzum8 zQuP-3p;r_@`=2iN)8Ub$e+TznToVfLq0UH(l~i|#&Ci_*FT#d-wlOIlpiEF->ql{P zv{bLU`Cse%I@ejeHt>D2t0j&?S&M3q>-!3UHB0&m{C#cWg{jYrPO22|Dg!d-P+A-& zN;FsvEO+?RI;&B)vO5VJETS%O(N)r>G8k$&L}nOkG5I$nkSt)#NdhPxLR}YRM@&3G%HVB48PP8T6>t;RM}M-YATV|5$sC^hc?Mehjo%@RN&b_d?lvYnNZVk z%Q`t==FMu>PZY{A)x>c)8Z@#FJP7*H9o&M1&nLAE@&+bSk$)u1+WY&`B_;t5*iv5d{ zD|@pIrC&k33~6qn*r&8RQ%tXjYdyL72!RzQ+4(a-n@zao>)ek`=G;#;-_xV1>B+^8 zlg**Sll72sHp~M_1|O}1d%#|J25a>;`+A9_zvp##S|C7`El*LLttEwlY7aB^`>4PX zNP2M%4`BtOte)5@(4WK}-i)Lk-HJ#N>f5Jd=4V=P^}^F99L$m1Xo;Jo!HFxeG5|V( z5ScT8C%x9@S%;Gk&E|Ya`~R?ZkHM7&YTKqeHab?vwr$%<$F^ZfmvN zon41@tuM;-EGi8mrcS@VDG(CNX^@r(a_-tjHw~8lOLS5idgHt*Zp3{3Ztx@Hk*Mg& zie14XDE1sE%Y+5gp@NlF6{9)UB2fh_w#!7SGeFuKpd~8{h%{7-62ywWYasfU*<4${ z-n4?b&#OMM%haGR1b5I*%>f=x%rB1yEi6}5R3O)&F1*24_+r?kKOhqRBTq54RcZo}%~M$2Efhzp>f*6`eT z2aOhG)tdfCM$*Won6&RcWW#pqs(&&>Lv-Nd zN*h02GD{dcbb(xwMXBOIHN5K$WGc2W5gs+Z>ulVgC^n$m-CThj|1yXjR|h6NIot|= z^5WOUt6n&C^W#IWfaI}B>A5Ul#BoGhjC(Zymq}kl_k1#zEd`C~Ru0n%r1E?sc94Xs zz07==Cie==#=5|EbtRHGP#^zdOl|5_l_(=O$AnTM(QE}bkSsZkKv7kUUs=G)LdhtJ zMn&u;JaIvng3M$7@ z0@=%X+LVZXx71V-RGey^i_=x`f;}Y2JLyS+D?O$2^|P8LZU*W7NPq0{x%+*X>WY4u z`KN}@huzmVUgn!je=y!5%1}qlW?26slE)Xx#ja>mcaHZuXv1ma7Pa=i1uZ-J9%!+E z*Jz#@w9d(zt?V9m-`$)Z-xmT+KemoPxP-1?;OAF=ddm}`R($?j+Rt<5QbdWld9%P= z<;r&Rb$K#?dwM=Q-Pi?K5OS&m@#SaZ8-pCPPv`pXM214VBsp{&+sSpL2WPE~E!3 z(a)JqQFePgn|j@z)ZI~PaZ?ALWv;ltpFcSsyB4fwyI$$=c);3jgCXN(T~jpO7#UnU z?E3EkT7erR_BjCg%p*OcU&HzK;fI9VpX_+X!>(76jVjt}6EGq$v@-(}HhY*J)Do%g zcjHqoyx;grfO5lE8D@v$8MhXA9|?gQXLyQ3M+!&UP7;F-(5Zj^{K>1Ss~fO$_hu}F zDok`euG#ARcs-(EPBVI9%=K4GI2H11HhJ}HAQHcf&QE5#+5$qK?9h3>riHKT`ZJCX zF1Yt=UB&B)#+%$2x}naPUS|lY&h0F3+5i|IeWH((j*sg&mut$m>(%Ss{fqIdz0XZ5 zf-Y?qpN`M-{Vkvpm^nQaDmPVuaHX63k4oScC*Af(?42)QJOP)THx9b|riU(N#mCIZ z&uH*u-tf5LN(ydgzTIqBE}%jW_$@=XrqePlEyVTRA!Z6&`oIQ+kt5tL2D*46U>(1N zsl=)@FGEb8sJ%)bG<^SX018{lmNQt#Ae+5WzLz}>>;?o-39PX3`||PAo#FM4XhfI> zZ))?~f>4&X-%6myB4aBUOISrab2xes^AIs8GN-;zzgz!y?$385j&M{V9|0_{Av7*b?*HKMKB}z($p`qBa9zz+^ zjifPm!DLHw9h}WZbA-=2UCB;{2Z>9Z1~C^INW&*M@An7!G6XWDDg~J4%n&?`ge}v^ zz%jLj0Mn=e`D*J6pY3fPe`jVCI1nF$WGY*9Ds%j%7nbUpM^`}Lh=){{#}_OiD@4Mo zv;wSL$f(95DbY1tPqN#{J}N7vNkoNtHR`l?lrl(Uz@3YdpEx(&|McL%LY5Lx;cbrB zoWv=t<1vNI_?!oUQ=Q~eb}O^|VbgVjjTW(n#pQAYy6mX;Cd^QZGalfOVg5zdVi;Ez zb9yBK6Ur%uD-H6Tn_Mz2Glq-3u&^hu^5@i3^1xAJ3|}s-AU-aZ9q}aWF%3v#z#TE1 zEc0B@pFXP)s(`^2kCs9IHvZgD8l=qt1aR#l{xE441@XWd?oL5Jp?OqxQCxP!@892% zLT~C6$*|TAAT%HhCclr1Ktj8SmkE`7?>IWAmYOJur1NH1(Bmiq2M&ja=nZiFTh3e0 z%WeNt&Kp1`3tzb>)=HUos71=}lkfyew0wN4M33s2YbUN!4od^lHFFf%IgJ{)T9ZD{ zX2@+5Rf2M?H5_$BQ}{|u?xPjlpO9NqWk-ktk7bDZi{(+IU zY#k7BWde2-@r_Tj2gDS#=&vy$4hLX+Nsti1VgLC_Yr_^fh0H|u14mRwqI`q}$ZzJ> z36OXDjPejV5c5;%fpxh~brRTJ*hfk*AG7`P6kWHgf2=oBD0~UJ+HR~RK;MHM6U1`li!;?1ive=8@kXHXPJ2(STNuq0$$f2 zsOG9}vPq_6jZPvn6R3^^iA3C06qL$Ui~hbFWXQxeF{w+d3i2!8Acdf9Ah4>+TSDTR zII!3+(l%f8gCvl`uZ(2a)GG^I8Sqbh&y0|0KW9~37Vt1=%v@lk7j~Q{Tpiyi_xIHDIIr)~+H;`w#4u{)p|1I!)U%^)gr;>{G^-$zjpMcpY0#R2^ntFRw&LA0E z-%$8@vtua2_Vw~=#jn`(t@Vl3lfvH5Ty$yr^laXA`#cfSJp}ny&0s;0*bGp_1(qX) zSepi%3cAx~$_u%iob3ry)hCjtNzWdaD*B$VTT8wsD)kmp3n!3>gmj8T#uu5Asn!|~ zltHg=Uf5)z%agSUI8uLB+@1H4SBQZxYH&ilh|*q+-OzUk=en*oLho!QInUj*?vCzebWDtdx}T(( zXMSo!c_e>gsl)fRD1xfTAWHs(=0aMcELLr#4EfCvRXTKLVSV>4n(t+a;aMHi90qygsO?rIBCLTO#rVa$QAZB|2r4h&>x>#P;MuV z(u`D31Jx$sQc-<0YvKPU1i`Qwx8_&rB_TBh4V%eE+l7Byn1g0G_r308(M?B=wRqMF zMYrq8$^QNK>*9DMn|Q`iSOQHCq49f2lWh!6Ie!?g6DkBJky^GLQfQ>g0@k+r6+tT0 zU{S%!FC-^I=@<}Wcb<8e&x@v zG=n*#Cc4^Xt^$2mhhwkYbDK__80+0N_8RX zC{P-{K?Ek1HZv#W=j5WpT@fxOeytn|ZD}U~S-9%V22k@<05z{&@~P$x&miFbs(I>9 zH6Lg9spel4L1RDFJU0fPnCgG4dC&h=^O2uw9%CA<*7{#+{;um&%_II*^Ld|Y-VmRb zkFV4J@#Ry^7s~?wRr4@yCtkPzR`ZdhpK6|c&BVa$Q_cVUPc^So_KR!U*mSfQd7Xs~ znZX{Q=EbF}Xb7OVYFB>)P7$ixKh->0k0#2enn(GEnvVt>vqCTjzaq-PYvG*{qqasi z%Q!c8Jrgt-@ds16j~;7Q5gRqqx{6XDeR$nJ84+Wauc}+H!O(T(GCf`knCOAg7m?=9k z1O*LT1DUWOM@A#&6VLNlxu*V29I8cUDM`9K>(89xBFK^-bo#ZEV2xVBz zjWoIQd|IANQ1y3bWl2aD1Z75+1Tb6TP|4j%M27*nI3>9-vS$=IrJr|)9f7|{ijw>{ z-V%OAvt@A_whgLu)r&KBW;k$=c}?C2=DdK?PPwAf#kkrE1iCMGbKM;;-@uKW&d>Wa z{}HS%=DP{x>Jc6uuirxLKY4{Kn zqn+qf4`yBD$^s0bW7aE+jp{rkUMwyn^7YY_F7FOY{PM}%48y=W$LyJH_7tT zII6OP)J858*7af5cK?0aN~os=y?i=g@RAbEw%F_G2FI4h{=GZmTKPA}8cOIEKxzr6 zZ;4XRF$6nW-8M^wd&7R$t&dlItQx25JGVJRlKvJcdSC$ChWD4v7X$wfn_oc<4)z__ znJhPS(uV=Cd95s}Es9)o&V)FkJYatu!R@UVSYK(+cpj{H({HtB-v-P1_K;8UxpO?q zU{P6lOdkgmA%_GU!o$91+}CFgkBHL8naG2>5lsJO^ATETK9K>)RVkIB3>bZ@=l&AZ z1PrNfdM5}7rj*O*ABs9TK^0mB#OZCu4{rcRMpxm$G? zJj@MJ_d-g|Eji#5)ygwK(@JRxyn|M6S`BhnjO{l(7$4-0-@F@2STQ?O?;uEB?#bnu z|G23mchTMa=enk>6LPhQQfD>!?Wnba){TN#Up@^UXRdcY=+_$w?#{FW3KfU`r6J`Z zR7Q+t4L2s7eBo3dDN%=-WP>5|0Ia3erV#V74pSPoGL-nA;#Bca=Ggffk00yaKE;*& zPWT<$xdxpF6cv9cplF5nHqVkqMUiS4Rt5g-0M<5X<0d}WHoGbdWr^)g*9>5M;*(#| zq~6zXqjwT<(unFmun5KAu1|kAQ@`g#jH?#fd*^GQa3~U*dY`vLIF74$-Qg?$@=U~L zabrXrQx_qZP@7@<*yYudjqmvG#$z{#aF@>lS+xXNBg`-ypbdurZKxyKPl5hr(N@4~ zF0$zf+%_Mly7x3r30OGO2`y!ry{aI?Z#Ce6WE5Ct1Ph{=rzlAwp<4|{8&7U_BLf?E zf3ov{Qg#Q75hG{NZa>a0ThNOzuXCu99Ia`9es_Z)8&SA<&^$j_+O&2(Fk(Y}(_Avd z^aVaqCCeD)cviu%6dmmy@_uozRND`kdwOm;}rEnIj<4;+7Sg);^E2knnv(5rG?V^vlQ+%gq` zF_0v)ipnt-th2_y6-djkqRF6D#VE>Dr^gpy3+ntc*u;LEW#+M5RLxV643V|Fh&dH4 zUEYpzjtT1iVb-BB@rmU-;rb{UnP_T(B)!}`wZp8I|7h6RqKDVsd%Vu-qIUO?F5O!U z)h^zB>t4}%k1kVY8Z*4#H~md3mSB5QvWZ2aEj=HAran1h9js%$LKdC_BE>i93<>r2Iojm>t2 zN4yF$rNcz3AG(v)1(@9;0y82>a{XKB{yw*dgsn4_rHn=|EEXN)xAYSABdaMd<(t~D z%WTCF(R%Dw?mUN4{3_SH)0+%rL0IBG-}<&}VIhdV;MT?HQaJtkU?3tplt5vxD3R&) zn&A1%=g;aRw(G*~jU{v7rkv}@0fH5Lp)cfnlo4P_=yywtm(aia2?lS-{Am#|}$Gw&{dec#DeZ`(WTO%+%}g0Frujwb!%AY+2{+y|wo@jR=-IMY01zp5=Ba%_3WSTBZWI!S4?rP_a>+G0D|SogKOppVxX z68&n!qTcgh%&u*Q)s5e;mFgv0XooupWgPqx15N-8a=a!;|OqOF)!0JT`WF2 zUr$J%a$$P51hYa290Xe%qD`{~St4Rc41Q=tTT;?ov`8LS)#u>={5_tM+8njjVoM0n zmJBx$V9tJ=7NQJzxLlO0(TOD5`F18{koH5K&8DL;Rf+^t9>_lW2TU|ZDJZ%2R z7|GGL>>m*M4cWPLU{3}9gU`h-R^?0P@Sk%HO*N5xelHYjJ0ry0-_nh12siN}-*a3S zHJShx{{F9pr|7Tw`+Y2$?>nl`gPyW&!R#{a)A(4M)zz3KGJhT%UED7!RB)m1jzc#T z7D9DMoGz_#0(;P1DWoYXnJ6<@Ij1)P5o#P#Ji8B zKeqxOqnc1;r^Tp2Gte;pc$sj_DBErpOA@RG#%V*B1N82Gn)`0XBfPh`6&X+l}{f~mk(*9b1E;(sPi1G+`J!(<7s zTo~@;zH6*!Hn?w>yyXQ2qO`*Dx`4`$hJwGpjN6vM+@O|_Z_MVYbB|W1 z_w#Y}%7brNb%0jo6h2coGD`?j!ODiJ+(D8tF%rN$kFH)(+@Wng|2PC7()igRS{K?c zO(I0z@ic*QK$I&u6Abr_1mv=Gjti+o#G33Q$aE zib_+)1EZL%e${fpu#Z;kFg$spyjNoZL}_@hpm(EI!N0IJqNr?H41cmSV8w0)cq2Az z*AQCh+TYhPF6Y+Rs8d={AT4#=r2=7qn(T%H8?(^kMOTK%3Yb&(e#1{!?WLiPp@KvF zR>;OO^I{d+h^L_j@=XPoe*2y;w=VJmHL-HFb>+loA|;TC8U*WD#^9ylimx?x^D z?OHjRY<;=}qXRh6@DLx6{2fzX@!P?D9x`!tq9!au09_0JZLWO+rX?!ga*Ue^W&vq4 zBS;=GD~N!=U+n%n%JOc2bSnq%VK(kmBK89ipOR@!@A3=C%0_54K2Bs~$kXVIxaqkZ zw|wOZ|0Y;j1gbRwN?;LSWab{^MkmUQ9tuo#h?S#HWzITv@B3hXS>3Tc_Hc93qyFF$ zSWvo??1#dbb{1m)0z7x1%^Z_t=z*yuoW_fkX%nn?epryc_l@ud%q(i7>#hSZh~NOe zB!?HfIu!rT2{`ka#cr*A@bWlBYPAq-@x-BZ4Kc_H_Pw}WP%nwRmgUb=!Jl>kj;>=S zw6w-FhbzblNnIR{oOza98K4Gd1nE;j`^Tl->E+*lG=Ib=RYj%d$dukJG$`w=YyXr} zqU|U_Xxebmkc~g9u4+#DnBwF9HMH{O)@PDlvgW0y3zOWqH1gsD;eI?P!6lGa;8@N4 zHz3Pd?$_vcjuM__P-*#ikrUL}9t8!+aw;2VuqVKeal6MEQ)^!{CrE4O_vn*2M& z=QYWMS3MPMWEMF)rH6udoT|_GiQ0`CJ$zvAmFmtT4Z?4F0_evRG&o`f$YxvE010^U z2!SNL{c^Md8H4VEl2kbZy$n0PAdDzzI_!~*b;kZQB2B*aqwn?EZO9-G4R+%jr63OC4O-|p1T zvnj#!3X)?0!-|FhsKW%T^IhNAK{rYZ&C!~|>WOO^>Q$Bk5~WEe$mN&rc5l0z%v+@F ztf}-FyTdNC568JTlt2Yc%cAJU(I2pN&C+uV`p>QR3g|LQQ3^JseeGCQzBuh>9gRW* zFZf_vC_9NZ)bn6i;8gaa^%v*P-A@Q*Hu=ThjA`KL zZqfvC5OU|*>~P_x!v8q5pPj0lP)P?vNOJC!fOP5oh%o{3NSH{|0b9-l}YD)Vj8)tpwgD=A~L&(-jEF@QmE*0ON`n1 z&CsQm=+G{~K@yH!BSQi%f6C9!_*l0fu>;-kI$KuG=QoW|ZpTbiVO5%wrt%dU*t~9>CCBbYmE<(-=xvAunj~qT;2q-+=)4hDubauK zR;)WVwg3-b=by|hTzHU~$@sl=s!WOzffu*&La*=HOz$R#+M`OHQ1g-EHR*ao_-o&s zo7;Nz2AW$;NRK@HoA*^>h|kXlG#Vm z>)OT_j9(LRgb@?m!1^j7<7epf)_{SiGe2YYA^yxop>icNmkZ{FlMqdh4ZFo)M}hz8 z7D|lGuKQ!Fm^01-t#rLhS&iJ@JRj#e=`x&i4(MVeb*=Sq`%Mj4>Nr2e-Z(1X9{y`X zl-ae$Uhgr~`5!v8J}ufEhid<8^es-Mr!CC+NhT53Y2|hTTLj>5NqeEODY7h!OSN-Y ziH@Lbdk3o_isUwnmsYp~(f19IHU-sd!8@|6rg%5&P@2ZEYi5`~njdAja0q&-0pDGH`~vGQRR5>k;5v4C>T0 zSDZeyO}#iRwI@KMSKBvH3CSay`|LmoU-wXY+zH#q%@)^WSkZ z;Q8iQy3>2})V$WV*X9`y_6Y_&epr^r%d|)hktPH*{JDkMam|!U(62be)sxIWcXB}j zo6WOcnhVw?Jq4q`Y$PxVy31E9)Mkd9qg}aFy0L|0Vuf~|N^ax)s?xiS6)naO7wNM` z0V`#=$AK=ryKZu8?f)u$+xQK=G<%^aV}1v%5d^Qaj%bm<;Skux6F#{?%Gu?|6flM+ zyxv5nA0}!}FhjHP#WLz9dB=xJ5>(gSCdi-&ilLLttlFOEyqnD5z=+hxlzC zVbv19*3yfYGQuhzX-HV3%05tLgGJ`HN6c`)zrg-@6-4o%5GPrD3x)krq%G9aGg<{x zY-)G5Y)s}8%5}qq$fvDdTaPjM=HlLReplX9d{&y^T#o_xcQ>iW+~6bN9#t~@wt_D@ zZ$R%F-IwH6{D3j>`@y#~Yz*EKu&Lv{4%h3j&O{C4+juPopn@%O0ZrtF@b_P62fSwFw>Tg1z!-V;TgfrRzgWxA*0cot1XHnrR8D8B zV6u>hDYqIoxF``w&SZp^tjG+~Tna?o6${IW6^}ls=)mB^>mJHwzavS^H49<@e*`>H zHRerpffLGS>luW>7baUjJ1_ay1ZoKPJ0?&vzlNere1acHFR;rHSr-A~xonL)?_luneuMj^JK2N6I3JGIGmI zM1D)>*qwaY{b<6#acmW-T0j*}EYfm?KCiDxhbFJ@2dr6Ko9Y}EX>9w}WM)R+I>!`0 z_i%)%(>x2p5AWFQJGCNqctSYT7lU=PbLB9cYeU8m5<=)6I`qUNLg+Uv?s&NmmY==k zL=`8Wwv4X*6F=~xkHw2n;&n8kdl?ex*sI5n91i5U!K ze-MxyK|EKGk>K7=MPvoZ45WnwqR9B!XN?~XU8FiNn7w{U*bOqc88RmXv84kyGH20(Vu7I=E9CoWR zuru+d@}?XhDk;E&<*{2Z^7s60wL3BDx1a`4dx(eIA7airF_O$@MvpYPDZa*np(iRC zXjoF99JDxJJ@FzJZn~<*5#m6#dDXuj)Ue+kojr9&9DGzk`x&w;BSEBu{s;@;LGQHw zK6GR(}#~umC#*v=rL6OB@CTqnaT9s7x8*HqWKA7t_A)l}8<5#ReCJ$Cu^h2q% zo{AW*QqsED@b!dajI#JY%N0Y^X?{cE6!%C%GN!9j77x{;tJMso8Rtc@h35;xSNbPV zG}0YP$P#J@^H9{)^Qv96^Gb>8tr_s8GeOtS7HC^?01>Lj0*d=rgc>~{zy)Eo8<`I~ z|0t2lDMgV>JlfrMVW6d`dK%4)SJhh{5*$?G@gX8zh;*Q}2m9up43D5f;Mm zdIKOr_hkSg)DR#-_kNr4mI>0AMOwsN*rDV|LWD)q6?s$a!BE5%srC}Z>^aWm5u*U| z#I??(sh#1(c_hQXFp~p0$mGUIuy&Vbn&}Mh^Wt@IVDcw{s9L9&^F$}u z9kZMcEfmdx{R#BlS>J2qGa&a6JVSg_-)T0db76TY*|FYYK|&IW!b{#21B{$!u^1e* zNe?s#ixXfc=PdxdQ=W}h^|+j~vJy#>+m(b{Wh+szwM*-bv6IC51Rh5mSro=s5Z;^= zi(CR1jUvv2Wm{9S+qMzjl?qv(pHONVg3XB^7T2hs86^YS`EeH*PWM-1DQ&P0#qn5LxAx{`%2k!=HPaGzfGCjlZY zUa@IfaQ+zQV8_SIKH9pZbBq%SyX3iJ{R(Yg6nHJx=2*wVc)O5(QqxE;&xaqR9*d)W zEl`kj^vR%9%-lq*gUlPGFjLz>tBgNKC$J#PyxlMLOS+Ha8K;H{n(%9`b|1$vCjzd>r86VB|^BSsL$QeEfVkC=r#z2 zurg{PbBA)HYVmbZ`C8mc_z(D^x3JwH(C6qRYXgb&jN#OIg2#B*c9qn2pzzAAM<#ql zRZy07G$Tx>!SjHOk(S%RIO4bbGsThlNx4O+${RRyk;*rPiD+0VjR#;IBtfruZl3gZ*ou zNi@lNZt%6sjz~|+NA88}r@OO9aIB~M#@u4S<;AJo>&_Hi{7rq4XQ5_1jdIZ;%2BZ> z1yV4+s>By`3ojTN(n<$Cgxppy}y|DG6@n1l={v_xdal3^E0NaTO9P6E= z(LG6SuhRBaMAW{QV=d~_b_QYsZ09)EpX+({{S#Mt$1O!~e0J(f`|aX4WD{q1vjwCR@4u{FI$w zm;l*Xf(wwHT)#eLr|NKgNuL)n8+oFB9qc7NL!&J*tE%%O({I151BEHv1%)>c7l}s_poOBvF3Y!WWeEV^>?XUX5rG^CMf2LfMFQ{p68 z3@#$!QR07f!uB3_ho5kJYbj!xY4h9<;!^ZXzjxadNfb!t8%9KoGcN$ID6aMJ3SZxR zq1L2oJ5Pw0Ws?{O#SbS>*eB@=3RvwC zY$U}j?(FDC2jMn*b7xeJto8Hh-xIN`>4l&ielw#C zBd9e2L}geKnkz(}HsB6vMh(v!1)of5Yr}akmTCl($0TwrssKI68h7w@6E~DCB{o~` z9q`mWzfWwZob4GUA^kQqmmBxSd+Lue7)q#wc_|LZtW?5FTgcu2_MvvYAP|L*IffiB zH>iSYH@){MD)m}u`W9fZ=VO$LQt-)?rcyn*R;K4SBQ- zy|qj)YV1lGp4-X7wwb~FVb-LhqZ|V$8FvFp#?SiyC>a|WkE6KHrWTS3@K(x67a#Aj z6nO5#^EwWXFyL<(Fxy7`EU9A)@hgs5m0}a;`;uY&W$t;;&g#bDi;qJp=*aknF_3jK zi7M!IVh^QXt_Kx}pL)I%9jfs)#te_r5kim&!m4XU8MS>Ernf7gh9=M6yfx=qQzS8+XDku z!>65pSPr_1?in6{upV}mNWpv?YdJ`lr|sUorhJa6%H4}R0Br?8NV#3&R=n3UIUnK; zgxf#IP&DtrlA)%8+I(C>9$+%Ds6S)Vu!*hlZsz44UMl1WHM2U~~A$XxfKvLTu9K$E_kKKcs$trW-QFw(J_)>_Qhql0MPko=)gNGW8I{~Cn)t>vVCP3VSrZG5+uq(i3LJio%ji$LaO;IlAdg)A1JPbgeiDjYNa6w{0 zNL-DhOsz~+r+N=&;65mdy7v+fpoYEaKUnV?5*Q$k3DWFs0Z`XpghVg=9d4 z8PyKqErAj4jiXVlZ(0QN6GA+Y+6IL6}7ES4d-GU`;GZK1g+waBa$ znNk$j2x$V{D$u7#LOoi~T4~VpK^+5x(8Y|h?Y~NIbj;^prvagSN=xY?##B+Lo6G9c zS2(Yx#rcNd_voOj_I=Y@YY{!OiH>h;iRhSn9O~)4nY-d2Cxl>6lv#`d;)0X~B-s>! zAU~p8>Ln-~5K_O!hE;f;sTgEzd@h>1^7#OI#z&VGS)L^e9ezXOU3>*dzR{^amD^TI zUYPQuyPC_ZmQ};K={7Wydzh&iWq_sW_B_V?+}`3~b*9C&#DMlavqkD5f3}zFvhv%4W#72Qsc5IV-JtCO8&lDyli|Dk}m5 zD_=vxYfTio65V!`gcSUw&8nYhfOio6zCl5e+qM?AK@E=D;kGgDd78$qO+3C18pcZS{9yNn#k@!q>B3KLAvKf&O|`F(O9Y% zSuD1GObq%2`b;6{$3G>US*fiq&Mz-2`%j|PHE1fZi3TDXFAAHQN}3YPRon-`L)kEK zmY_LEZd0hMd^$;HluVkl>l*I!#-b^L1Pw=N$wfvxMfYYV+qYm5OhB8X2GGeRzHcFY zAWBx}U26)CKoxY#D}z^(CmnlwEMh~`pSyOIx>7wfa0e!th$F zF(^4p0q8BYX{1t6w}k7cJjiQ~)LhskI5l7j*A5Z6z<$Vk+GA)M51H8XXxg(V-l>MuEAlyvgI5Nr z9fUiA2#cMzSwZ&sVYOHTza;pl8o1>nsgR1Xd|`SRQ*g9u;zh^|;1Ma7zqjEJB;2-O zPxnOEA;{A)s!;;LYJ%*D2ef>d>lIkt;#bSy2BwT1%4<_43`ztx?IWO3?Atr!csCAT zJ!%mTpvj;one(9UMV`KGt&eUFoZQacUYy6v+|k}iGI`iNQGJf$ig-6^vIindp|K04Tc4M9R z$ZBd3yHwrG|1OjA7h6`Wx2kNRGpx)AH;zdwCy@OMTdwSzXz4$GVoL`Aw!Dx8K~DIrlK?th!E9|=EIGCY{Gw( z-`rRq*;g39DY3q_$J#?P3(zw3zGfKtE$b>Z?W&2HII&x32r0f6J|6>;M7c2+LcSa& zZPs_t5hX)T)alrj9a*#lDf5+--TFSCoqc_fF||(SmkR{;U}L#~W4VF1r&gJEM0CR< zYvw9Pe1(!#+I$`?z*|DcDKDU&v4dM|+=YQQg5AV$>I6i4<96BEE+*ksyxI7B(5F2F zB7|LT=vG?^L>@0yMa3P}FEI>?yG|KbWhGy5n`PkYSs0e6fDjBpFkNxOCNSH-+^SwG z8$|b_WYuIgKgz%6USz{)tw|Oaugd>eM8uv$WTAFoDOQ}r)g)=iANYx?;*j-qPLY#; zWUd=g(i~M`8*T<%ELMnFPxpbx*Is6nUFw&@Y zp)ngZaVkP)f7WQzG3$o;LvQtjwW@Q{dY-OcKr5Km44I9If)nC1bv!gdAa5oAX+bL!N++5a%$sHN{ zDcJ{0F|YC_D_VYv)8ee$?kIP~+QUSN{&r*yhy&$GOQZXc8ab@NTe)B|JIm5gGvAKn!RbBMpv&{IhM$ zdm;hnnv}vMI)zyiWN`9j%1`UxZDX19JXJv3_`pwB&+xNtJWhzY251|bDUhLTeVI>O z%5z5z;Q+Lah5xpVxmhJUaX#C|7XQ;WZuo2)Cj#2W;8=7V2jCf>ZR1!#+jzW&)1gx41=}$^io7j>e7>)dG@;Uasu_c#+Ug&>`OD^BP;*yh+ zQ{A6FOH1g3?4RP2dv}jfi~C^t29~$y?H1+<2JhkO^fYpwFZ=y=Z`(HzZ`zmV4f*x% z?C#jtA;&uK3sqVK-Tw+2&l(<|CYDYSX9alL9aZjCl==>nUB9Zdx~YLLv(??buHGF_ zT@6;VUu|`H++%EY!;tc_e<=OZj)NK<4~DRa?7?LB4K#-!XZycT8tVi!^tY~mCXG{Y zJQ^6DHU5z_j`lCV{y&q(p+Nsk8k=$Foh?Msq|X|0 zK3$_(Nq!QyVzBVJ^2f)#y9<9m*EnJ;2I=T7peexGSN7rSd2_z)`zEvWL6^f9D5m;h zyKA`EqwG;4}!CuKDXyTO32wR`hzP1 zJVEWl`iFbOWG^X|QP;#q&oTzxQ--E>_tmytOvi5!d28ESULJ#Mja+ug|2CTX z1!6@GudKpugN&`g6i~q7W=M39<`EK*cP&tvMKNTrZ|>)G@rh~-PHVD=;`@uJa>QC| z@8WKQJp=pL@7WJp*svoAuOogm&{;T$(0bVfyPpOL%YvO7`wZ8d+` zGO5t;Pf~(x^ZzhsFR~iNIFqT@tuoOIHf=$DraJs7yErzooTxP;qOd7=-)LS-PDqh> zO8oO|abYmE(XDL`DKp6jZK@``$AB{q@~?)fb}FigYMId$<;upjL+mn$h(Z~L2q43zu;fB$>_5LrgT|n# zl}~-~G2l*p52(lGI{pqHX~y;L)ecza0{XxgF~w-Y$-`!po6}2Uka0uMie|^(yKrk6 z4D94DO%xEa>4O3vlfO1B$sjL^toG6m_6shhx|Zg--l?vBC4rtWES9}@O29MA3Fm6k zpcSmO6B7KUcmWc-(80|DgED@9sujE&hlHW*!wgDRz7P6cwk9zyPw3Ue`?~q+v0-G| zlZrMwLBw;5_*`*~c8QRgLt&aC{+iCPb_R^7eE%2> zu}iY=lQp_I?D(K9N%MUXs)?M>MT8eKtZM4bmYL-W1`LXc_Xi5mhcdwV;;L+L z7g*O_Y9K<2=Ke~nz4~LRMg(@y#*<7x9e2kcKyivN80!ZUJLo0#AYcVSyQ!+NQbH4t zsG%w0FrYCZWZ7DxH+eVN7{i5lok@_DpZT?Eh^u6f^nE}Jopzk*FS0mV;gbb*8mYnu zyexM4)}rYFL1bBmKusj5uu2LxR+x|6S_bq?vMIt*r_^b2}Nz<&{WZ-4fVo5m`Z}^$0J>V@e4o2Yp3X= z1UO|cM3ge4kgR$&5fDUXIsFVGqhs?c;W4yr2}XE%NW>9a8nv+-Y;;|Sq5+D?@l|>v zQ2$Xx_FF>)i2@Xn7yng6J|#~=mCm9r(~twJ>DS97#aSafu{xZBF=<_k>rVBrUCfG~ zTQyo1@LQ~}ONnc07vLs=z~%QwKSBcDj;vFvUpkgyiXSJnT|8tbVM%A|FJrUS+4yV~MT;I`*Fi9=6`ViM z@5JV&+RZ))-yWrr-stQj88sygXH@ETflj?@+Q^R ztJBkO6Iy1FNVR-KB)#IS^l&Ob&8!|k;0jTJNfBJ?= z80OcAKp<^sg!FQUZEubJr`tOowc@2#|BaYss=Dw(cwubkUm1B8AR~8baCy)6`Mc4X zRiSqiaA0r62R%UbrC%J8nsLm^dCped1ixeM=QSi8jbw1ao%F1d1>dm6kXG z4rz|WbEQmcU`&6qXK0pp-2iL5r5Jsl#zv#^z4&lANaVFM3J2~lmC0X*u`oRha#C}3 zV9v}Hw-}Mgl1*~U$if;I`qvQrtpHfCeXDR6tgDNWYrErx1~+gUFQ}6wWY{ z)}*{5#9*L5N-q~DLQ}WJ?5wa!V3{Mi{CPz-$anN8BlB+ePVs((ti_$5GHy_%d2mr(Ry?ZAuQFYXji5~yCsLgAchufb2l%0iO;v83f6F^HFQnY zQeQWlbZ2F$$K6^M2Fd{-qI3v9JS(*`tuui6;p;88gr=rTwS=5 z!FVVc%U2Qzp@Q(7Dw#<|loviy#N_EH8R#%WCbx8@O+=+SGhTplp?FrsECIfH^AE=b zfh$tYQ!D`=C;|N^^0YP?6~28jWpMonfha#Oq~TqHRiYO2kg1!gQ%2}s2UM135Iu2T zw3#e@}9g;^!GgLUGd+0w+xl0Rtk$>?}Ip_w;P`?0EnaDB1 zeSUKTk0bmFu~|7D;Nt4_BLVGf3H#yn<8@#v2fl(_tu>uPH->0&iiCYcd72UqG%#HU zzmwj!^L_v964vPy%x4u_8Ga4VKg8UGZ zwH~h~Yfb4|@c?Ipd#c|YBEug&=z=Od5aGSMS}Rcofqo!n?Y_;5Us5Y3Y6RZiIF~hD z7i9-N2SXE#JB0Bo_{ETZ7!GknZM4_+q11gkq?46Lc*CSD`RTmic!;qM3SXH`6PRRw zZYn!oG~q!{tI8{ZR2C;$ExXp_88=>H5ndJW8&lq1nVl7SL*%!oYG`SY!hIgSGKprL ztRon%9vLb72N>sLkbAHE?HgQIToHGfo{$vQ*cN^Hw)a%UvzU?o+m)4-|> z|L;?)-S0kbB=+vFTX0uXx%#8*gr)NG@9Wzz?I~YF2v;A-SoD*gmKW$)7n7wd8`=(sMt8bAx2GS9b{E1A@>hXbpidPGT8VP1x2Ac|w#|mHE8lb^sP z8M3LPhBD;?r+1P})H)Rj+prPA-UQXlc+4fylXXD!czo_|lluxwvmGIppd|qOd$D+# z$ZE}voU`Npmu12nIE1ENV6antzvDvfEZs5)BQ1{{d~8*Xh7$gYGc%E%Krmc;Y)|(y zR!sI2qgUVeSuO)Nm?QQ>lAcuf;X{wm2?k?!rw~1!ttFN|IwV`6Sv^;4;&t0^o?t47 z+uUwP;EiLQcMhw2v=Kj=v>Luf4LZylAxo&oZtSa309Bv z!Av`1$6+2yniD>-&3bCpDfryv#~c5$CMKbsRMj-kpj|mrWk))VrUSsP#3W<)gqBkF zKjVUfpyM^sRxPbb%tS%HRpN%c5f%8NY*XysFg|~no>LGGbJfc>+>Tr=KH&ii=k`-P z2}Up65oMQ>p68=-W@@YykEtq;$!KGSAEdd(9=`YB?0{}d#dz&RFefk!Oum9DLq510 zImg}{3wlQ1>C@5inkxXcMz=)`t`1kJbQ z&+YhLn<@335I<-=I@O7=V68yILEi!un!7wU5(#-CutEKHI&hA3C-Ya?UYozd%y*&k zp#Z=r`SYG*L_)(r=NO6aH>_&G-^x(Kg!8HOZt8R$FGCr%l9YQN=^v*VBpOn3%ct58VYHQ?@Q#X}| z09~pX*))zkDougX9J*MZ47cEi+xCoZ^!%f&eVmV7j<-~VJgT6;kL;DseL?CaZlA41 z_vW-$-mkptkIaJH|Mr~M-0Kc#viPEeQ#l5C%;}}i@Ecv-*PYcZ-FxZTQvFCqsT^|1p5=U*RIh)E$0 zJ|Nt=xnK99%^zUvh=|}^N3_JOv8qj*QtD-C^yZnMUGw3Kuwf4gy>cwIO3*JNK`7*8 z#s%^`zC5W~BiA-i8+kCK`9M`WH9S{ax3R8(u!XrP*p*fj6GFE^=tm?8r`D-hGG7)B zwbS1s?)B7coGi?_I9trNGhTfZ4B$`=r@a9;l<|r#^RpQ}B?@SxKo9SI$DH}lC{RX~ zn0cgVfo;0NI)~x5v-XJR;1${ZqOpg=t7GW)0(|-;Ky6^WDMHGMqQ)%>5UfJn`R7_9 z(wc1tN+*W*e$QrBq~Y;u^D9qrT>ns;olwr2(v!#3?dWI5GqVwj$CH47^9!rX_ok~8 zSG0I=#fdGj5c`8>9juEdtVq*kTJjDdW_R-q+DZEo7;F3OXnP8$$-XbGewv&zYhOjB6(4v>sYY#6 zNy}uNJBh~26FC*h$GQVuBotc3fiZfYNehhPK1(9fC-$s({%l#ahI2rsC-JnKhnqv? z$ZNBQ$!)Xh*$hxNj9HY5VmX%{$S!LQC9#ZUeVgZ8!B42+!V6oSPQSD~l!o@wW7#xf zMpR;W@dli^L?WYPSOsPPX7jk^>kV3aNf{GE=$5TXJ?ChTjG0N$mX;ODW-EB1vQ*3y zLtSUe#QH~qhU&^1liBDP8hT5i;;Bv0B})mm^l3zNpxX-iDU)qQ`u+K?`-)Y|@V$n3 zF(+Bc@qXY&#gti)GmB^FdD7v3CnOc@Y+( z!Y0f4-u_QE2WnYx*~WLmDpfAX0%x`Z-DAO^4Z!9M$BNx^Tvm;;Zj>Px+)%ckAyZs} z#6SA|f15ls4PzegXknX{3@xlT7Nafo7$jwML<{J5#aU4 zK+vLnL3DXU9&m{?b{MO7Cg;&vZ)XR#nM>$t+0Iyd| zu)#ENzcpKvkX)oAkg>XQ6Lu;&HicG_TbE}U>n72a>e7zM6)tRkYp|AKO<6$W247D` zK4pS$NWag7SVv>q47jK&G!k@G6?C<6vf9Y|aD4aZ`;rlc-uc!*ZOkx5$n6A$ZnBDa z@5Gv;EaxRZYB>|V`R%I4X5z`-!3|=@=1!vq`px1Q$*3{|UY*qjBYTyN*GyFd*q~NG z9*@DSU-Hp+g&pVR;4|K0*pCJ4EZ|l?#(<7Z0 zx2;|%4gJcLlSf(95V_Twh(gV!kh6)!fIn6fUm3o8PZ*DxDx`2zOf(`lh;XklpPy<% zVAaC)2m(Uq(o6n0YIjVoHD}s~_Djo%;+L*0j%X*=e#fhQByy5$k>WgWroMnDwPGlY znmmJN=tDU?;MEqVtUiaM%Zjm>=-{rRcSwxfisoeu0K-k=x}jO)}ZCw9+CRw<;~DpPUkpI}p=5MJ09L z;827LUBn8oQHfhsuLC5!7>!8{h@36HA$E7Y6Oy=vcOz-nbMFd6ZZ0UMMbY_@@`xM^ z1J`LC4huRz3f#*NP}Svg47vq+{G9&NKXY;haCqxL$Nc}p;dNhQ{yKaOcZ-%v{PcEk z1nhHstVKqR1?2Ae(Y*%<hs>GmnKIlN5g1{#bbOdTJ`7Pk_@ zl}lN2X z(Sh-RFm^p39_$4H1!|@n_!AP!TPzAvpCl*fcFdUkCIbh^Q?oDsJ5QBJ^DCQ)1LUbk z20YdaGExkdPNigo`m}`~doN+vPB9X3uL(IRc}J=a$LeLVx)+{6X9;3{Bp%)6B`yY@ zyoxASCJ-OlL0`1=Aao}nUc`s1MiR;j^&}}B_+g2knZYBqeo-k}v^2)V8mj$O^#>6$! z&u7(JY0#R@pip*RCxN2S@7=<*%vl-yQg%T~@~Y5xapPsEcbn3dY2Y#N`t(c}H`yb$ z;&l3C&sEkLgwL~x3^3Po)*O>Tx-N*7rUt#{w)n(!2u`gNHDujvq|Twfq4eYAwn62! zBPyLU5FovTzknUszY+411chgB?s5wjO_9-t+U2>Xf6OS~RjsCb+@GUWP^g^jI=Y6{ zMD*dv`XUa4_rA%-($ig8i~!=){w9MFlKd(hZwQ9-^RgmXLgg9UVu87}K3)pN(KgGC={r3IU#A>hU1bA!8(DN}# zby76*3BK)^kOr^w)v>mWL)9eMcA`R%ql@{#;|mzm$c2(etqCO{HjJI}R{&w0tT0GJ zKo!3>E5MMDI)D{}8>bUH>L}VbuQ;y@Rxp-<}|J32=#SD3V@(9ysS4 za|CvBn^MTTvD4LiSk81*08zUI!hpRksyzWSYl$sS7vn=j-uv+HN-&+_i(26d13d_r9 znV+p2wrjDu3(j=s)WEl%4XqDS_9=WYJqx1y-#PzG;L;p>?J_%E*!2Vek9dGu?Ei&& zzuW*&?+68Rs1y-TeO6g%O4m~#>mD@JC04vFoPpGG2-g>^zW~{WYmV8OB+je^^<_JfrdOl}xPkJ& z25U+QgV-omIi)RNVQ3asI? zxJI8;mX1t@O0TN-UOl{GdZ8lh#FnA1;lpCJ?GoF8b@6u1zdie#gOJT28eCIif&=Tp zB@D4xR}mPxh!kgw=4WA36KW1M>(@7|0VsZ!$P2|pVg6(7(YJLY_a?ooMqQ>_)QO4H z#SpbAYN|mB`Gov!#tV3EHF#&*(xr>pxZ@GwGG;=aU$21YdNqvM)xVfX@Zsn|S0fK5 z=rbVt0EIFGh*gd7PMov+92z!mL#DhXmo6U~YSInoE^+1SeE4)CZ@f)k_;7M+nNKR5 zON~Y42*(mWvHqY}o$CHSWN+i{e~`Th+U@Yd&RjeSJwpu;4i)M6>@8hSY1c|JsVF>E zcq*#9;%%QupngEvlhjn$rWoX~k7s8pq6NI7I%Wi>aRJ-z1yxWfTw>Mp zNR*3mbHv}8sFQ^o3(x<4`*=C!tkB$@?oOOOJqrIt-v|pdr*;s=}>!ev(pA`NICkL0<{bt13 zEec&V9Lo|<2sIA7&&~>LTZH_xt3NMd?va_8;tsxC<;r_ugO!qj6nvaV5|x-Gdp87& zAi)Imgu2;iNld}!)VVVinwuPjj1toO)T_YACCucmxL%gB_gyQVZY?0d`t}VZ)&LaR z7RE||=J8(r=}EnlvsiCe(K;QCOwnG~Z1}wQnc=F0WMS`GZd_eUdoxpMy}{;E=?)}X z&l)Cjz2%1&D2(ebTYVy~O5IKMJ54(R_FB>(DZG&tM@5^bEy;M-vP6Yy=H}1@wp;F- zx>RTZ6qit1o6(f6BoSL-3x}{E{E+R>MGr{(5bdW zqdEvFE#%l@kEd@HhvZkCEXC*&iyk-C^AZ;a6w?Z&uDh^wdYM>vic3v5PQX-eaWg z7k=NvY9phNai#KLC)S_*N$6b}*j`U2ly9K3_LiY;A`nH$SGu#nZa`34gzBCCfmjJu zQ~O!$)tsRVtZ8S-$lpP8Gh`M0dHulnewXZ>U!0X-l#n~ynR_v=G)Ga9DJ#Qm2AYr24!#^749EDsr?d3}Vg9V?uW zx=q;}!w)mmt(hCDFWKA_>kFE;d))zoE4Z?)n=`2$d;<-wAa+Bt4?>?|BJya`bql9; zVvD;%t4!O#Yxf;VC96Yyl#{F&Y(G9Tv?%jE%t^Cmwd(6T3asDhy=}npCD2FE8j#Uw z<#@U8SKcn%PpKl?K|59COicnk?eZi3K1cNNPFJ6|vY5VutrylC630z@svdb_zf8^{ ztcloWJIM>LbYTLoMr}cI98Sv^pvht-of26j)!xA0#a0AgX3cM;&d_RCIQ-lfU_?w`XP^fWvp&F4Qe|!}BeGqD2+Bu-Kq|SL3 z$V8uQ2)m4O;EG<-KS&9H$UGvsv`F3;S}TN4#N~TYfA094_FA*YitQa7&66jd6doVY z=5G*NxIkSOofg<3x~Ifxjf>_iH?Q)aqNdCi_3;SLNNL@V+Ta-_yNm~oms`L%!j|e! zziCu9g_@rsDyo42OM%Y~n>D$6UE|YqS*R?5-*7KQ+}PR0`)37(x+-O~UQYTHw#iCJ z-+>=$F5_7P9_8qe5eptIXER0pQ!$xfz$ge{!06$?*qN3?>UT}gwsCR2tMj0-r<Fn2C2CPaY^)ij*Gw}YR7RT!)+j=R?{ph4C0h#G`N2rkkQz?T)3n3Z=vT$(^L!; zeE*-z1$kBnLy(Ql02K~Y>&LI0 z6`&Hww}xU_J~LLYrgv{wM{XH3P_x#zs?O!nsn-N8U1l@2)0DKJEFATNWsYlmb}S#F zk;)Nr6064;x5W6K1Lv5|Y}%RTL5obA)aa@8v8?~Te9F1@E*i@3wDS0=TTAB$irFn) zxqUxxSlzi+;fP(^@TKU?wOoBqkMdr5`;@sX9hGyVaM7>>?55tdWuv}i`)1sVY{=8FTT$;ORu0h7zM8NZ_8T!97P3hc^1KYvB>y&v#!#p`m*l7v@c zK1Be}wedZC0zzIJ%(4Lp8YZvq-xm%Mpx~{Iru=GIQ&6YmUyPm2mISxlncEvn5D3br zhKbq*Ex<{mv30xL#Fr%cTYty0X}^XDslx3Vt#a7w47J}lVfju=Md zu4F5WSTB7mT%MKF!2#~D^$2WhuO>7<-1<+ekM^C;8pp9e*k+^6Y&x=%BL`ddF79q? zSA~gx_yl+7pQt3)cja5Yh({6WMST|Z~I|O76={pF|T6O#E zNkCihF>Bgy2=`blYe#Yv>?#=?1GKh2kI+r}&U9@0#kJU%Mq@<%X3KxfAEE2^K>l{D zkjM;8z5Yf+M^1Ma$_jo3U#L6Gd7KM6Xpw3^{tQ~EyUAJpb6(CT3@k==%CYd~9GS#u zZ}rf4Z`BlOPAsP)@FzO(jEnh^g~$1EbKUVqof#vBw2B{U!oUU)q1vj5RQhu?k#8l| z`kDJSYoH;mmTY6b2h5)h?2pQ-S$#=Q+KwO9*pOfWaYi0CI0~k@4ne7La_TJ zdorSfta-AWRu0}+cIsAodrkYgAL~78mr>3k-3bD=D`NkqrMa+SdjUEVv#f^;7`Y^k z3y#*II_9#2m&S2{PjWJ4RRQ6;N`&>POl?qK4B>wKHB_yM-%nOWuxPyPu|b%bGFy~P zsy%CAp)4v?e)C$Eg149*YDaIkD>BfVRNVWN#Mep!Hmsiu?cqtI0ey`!;D*2~7=DU4M^Cu4y^lEnkDRe z@n`;J?}egSK0B~?X!TRTSjqaEw|=pfnGiv{F%Ju&TgwMhMuYxg_}_Jey;b_AAp{(vFk09rXP zg-p~bnBlx-zh3HT?>LQ)Y^bt@l6&;+06?Yy#|rK9bL$_94dJ}RN+Ca=Ba`;1k$$wk zvW%+z1CO5s;ARUl{&KU+f4NyY;9a8#exJk< zNK1c?6RAv@Sg>&{AR?Ys*(3+o#ICprcm9mDxwc|?84FnO3b;c>t6R=f1LPhxr`-kd zzjg86f7iu2tToSdiZI7nzo-zbmSW7R-k3-+uuq5U5r5;?1-t`Z@(tm__xKk&vX+wu z)+H3q%osZ=ye33Lc*fTqR4(02en-k$eJE@$c#0MNReS> zlrA^yROY|wRBfw1T5ATUO*ten7?hL3>w}$1G>DpW<;8Z#>l0*%tq31lON5G_$a$#c=JB!edmHkyQA~ z#x~M%?@j6{2DAJS>dU%=>5-2ji+f)lHhubiad`OkqY6>4*&| z3t>WdV*IFnW0sEZn+{thpP4IEHefJ{h-YM-mtM>@M>RU^MB>S-#F0>5>o?n#3WIx^ zN$ij?oG4Zm`pKvBRzc54k!f1-m?B>?c)25R0nKvL?{{)}Q%7ot-y25myx9^?ak%NE z`|MKUkvnq!2SZmNI$(zi>IAQ_+5#;DNJlBtPg#eZQ)VXbN<@71 z7z44DfZYiHmTY;w6E(l)O8Z7VI}Svm!6vyyfXtroaXfuQ=3Ruc1E~~NW+GT8fJ4P?l*q&yO<(GBLt=MUO;-zwjMIB-E>Ki;gKis_P`ru+ zgR&%BZp5@elYH#?(xES{qB#^vA5eiBr@h5SyyghQ z5Jk6Wn_-NidD8hqJz9>-cg1N*I@!Q+McPwbME^KZ18>KIoGCZN8#k+9T~aPZOpw{v z%^?*vSG1Ga&lhh;fFw8PsgVWIk@hn@Hgo{Pyo=^kiyy%gCxvdf=wJ zrq1~#FYDVX2%-7-_)&wy-Yg3F`1`e?0;S@B5gkNK;RWV31f&C1Q?P(qj;=`{27OS0 zkjTpUfpH!R)5;@61r*z-hCE9|c<)ECTr1$-c)|`(qWh7Z1RC@Rz9oVf!~5p+>So$S&&o(3)2& z#o@g5W-jN80`a-rZ`%`;-hY8AC5|UCOP~_;h1A^C?h_$xa!mW-{B&YIo8vN#gUdc7 zRdjrLlc1nZ8&c87@IzrCVQs~0uBuV<=Z<6Ax9*umDc5Kd4?S8K%PmVhy<)hk5z3~H zljce=(q*y9o=XXjLof%ed60JNmuImP8@y#J>>EtESxng z!MmabjGU(neQ$9*^RmyLKBgtoGnTu{hY%hW0yJ&;rh4VI-)k}{sHh?zY+Y~Z%rbqriYKgpr6^L zaPtr8n$j(rSJO1|fdH(EV0kHTl`A}?=~W9PFs0S@M?~|0|Hp52gh9=={tT3Thy2#f z$_XZluG);&YJd_jEVH(m**GD{@#w_jMfQdwN#4_~q!E_5auW6^k$66|^fNh;YxtSc zZdrW2=sK=khS^!R4J!H4nG7d;zQZ#*GM81heI)99xn<_Vc54LyxWaAEz~&$}_SN-9 zQs~_=fEC6JQp_&-?c)U9GBN>W3hOVTWx<3FPT(4eDYq#e znMxUnIyZ%8m~MgZURqkDBakuXgXR%jsM!3=DPhmjaZecaU0U#rtBdT#wI>6@`1jbM zn2ey7L`;G9@a;pvwxJ0|F08aI60uNg!kvO@;}V85;<{ZHP92$<#7vB}A%`Vp*?oWEtRT z0DV}FC>cnSe3T~C6*aNqz-jrKI*h~W=GT$FQH$%f5if?ru9{&t#>Wf(NGJ9y2B*4Q zej1j*tdkNd${jn_N?lS#E|zjkMUF={jf+hD?)P@)0hNZZan(Y)Q zL*~F5Z4?i5ey@gpiX5dvd6VjOe2p=A1&KlTLt*NT0btjv>(?iGLC`@)~ ze;sQ#aHC!5M5A_0UZPoL;ojF%P1ITyg!Ha0iA&aSG)W+>rn%%?*bv!{VJ-f7nz|H! zIT_9)lMH5zyuBUZ9v!$2OKMZcC zjwv!FFE+tBKg12284ULaiQWjeyW_1Cfe`XKEC&Y zmr1TV1CjJ>?73(O?T8P!ZLJrtEfnzPr@3J`D-$AtN0oMZ=Ishd;Vl&;7o{>N!|FeV zeYmi!;>3~UQ^8jLvhe~#Zcev$V$EcGtfRPH(e|ZuK2`#4fnxE8c|;Nl>IQ7+yi?^6 zi$e@k5s~In4uMv>2Tlw1Mg%K~S31lQ{+?r`_uO$igxe8$d%t(>`w_!yaoW7kx$69! zoO+0^VYL)WFwf96g5y;A>tXXVU14JTZEHolR}>17?sZ1oGeRcuVwyGq7?4??G<5l~ zllbx0zqIIJ(S3suG~-&f?YjD68Q)5zFq;2lJu)GvrASQ=b&3gF{BfkaQ4sJ!?Gg-l zml+d=Sqv4d_v3ZM=8I=NG=t(iX&{5UB@r3XCx(IqWM{7R`NQ)vpMwICj(uMflCzK+ zUy4K@Yat5D_n+SSfN5yu;>v$L*`tSQu=3!IXR$KAxnW>XB!RXSq(Uo0& z?Qd`G@wc}&e8jHl2;u0ij{NW5noiq00eNsH;(3gxvO!@-AT=R`2d5fs{IhjItGq5` z)1>V*oQONQo4E7W=WXLAaiKM$q0|6?TS&`^i9=nt+DQ7k8eNIe;_eW4RY?dKi7FF= zmjO1j=8N6-{tlaDADew)OFGu2SXh~Tehg<%K0`zLV}$aci>rJlB%>P6HWN;5t%s4i z%GBGqNi)9tvlVblYD}HqN7sJdlY`D^#g~d~C!npwwu1FdOf4|8rEQbckv%F{Ncg+D zecb^cJzz_U&OY6mxy$h}SmO2ww0GR4yoAoVfCuj&H^?FSg#<7gh~_)*EOt&SCe=&) zh8-Wd>7eJ&LCB(X{yTF?`kT`V?4OxS9Vb{cIF;|)Ym-{mYWWqRHGc;!LF=&*lypVb zMa#Mqn3TkE9e&aY{%)rW1Q~c%vI?IFEy!kYtB4z;Uu-^A1rfJQWC3Z)kpDco0ET2F z!d5?cu2TO{FcFL~`|1+MmW*u)uu~*pTrQOqa{F2OAqqM=%r+s}%h#y_Gt_j0IcWg5 zW71a%+rf%5TynGGLfTEVREhRXg7|!~+&I`eS4;0?_bTw^0iJF`*j_}`pa+I}w&f-G zgLOoU=q^gH4f*v3_cU1C- zd*f%FB=~VRV*ke1NubDx>gZaxNf9hAK-SPran=%aXIRDBaRoD9W1VtF$;&k)Nlfv9 z0OexX;y;uNy_0uk9-k+w$}6h$y@UBKJd-q3hIOom`5d3lKO4Xu?#lE}T&a&VouvJp z8zV-+?Xm;EvR-+$GB-hN3>r2jZ&rrGc2a1cdcF;Zkk_f2)g?IB=fwI-$lnTc06){i zOc9d3mB&jh2w3LgQ8syw1TrsN!H(YEEy0^fYM)H-xW~Y8vEc;I?J~P!1;mYbz9*RW z;aiA|bVcN}+$nfx!qtAIGKw&XjX_XUYgv@{)8b$R0$Pd>RI(k%8-vEmY)* zlXWZ1?^ZHS7b1Uc40|Mh3BL0RpGq2L|;*;^}D5qol2|waZg$wuTBPjT_+RB#C*Ov+ZgP#*WikXVN^7{D zzokQA6&1&}(u=L}okV?bZO+ninb5>aHLbYdSYy;u!MI$Uw1ze;W9gRg2hVcE+tt)Z z{l*hIB~0Kre*@w94Db2GO0&D%_C%RAT_K+SFP9UZ)rs|AGobaS{&j5doq8O_FNhb} zkje?jztbF z(#k$ku~$z5SHHozNr%ODqGpbxW5c46(7oZds_p()xII|(vSuwX>hfm=2)AuQncu8Q z*+)~hoa1$9Y@oBrF@9|#RxCqbG7M{qD%mE6tI|a4O463c>VipZVzbRlbpV807S7v| zM7-;F+Dh2(SRSm{o;1j9%ao9YInA#3=f&>t%u8yJ!m)A{?AArG3rwvd^oQ&AIDdYO z{%X58Y;*kxYZy5W$nk7xAetq~GQVURNrw&MP7lVcC~lI&7oWRpqI!s7wgBmcmazbi z4&CP#S{RRJ&wXc3*`+|!V4{&M8X!{b7@`vRk~+hMsQ1g1iH{%?L$kM}u>b z&4L%c7`|m1yftclAGlNaU6E;vK`^?QSEh>dB93GHPtxhv;|0PJH+aTCx*$za}O>I-V!e3hd-|)w8FHWl1xJUNJ!yOtmAqhQSm3_n{-)tuX*!cSB%dk zDs-rfq0z&wqqOaVq>iwc~YGhMLET;^R;NeX0d$8 zyyUG-_mtEFf=Y%b9arXZ9GPiw$ooor(r*WApm2kmA7@*yna>zkukWn{4dCvLUe`p9 z>IT4nd=aX+(tlD21_>mhuZ?TV=8uHOn0<=-O?a0G9R9P1kU!ho&-2*`R_*2Cbbk-b zlpjFcCT8mhwN>Bgw$^P>B zvAW35>-DvHYDfQd$TLY@2M}&cmi}+ywr=JBPq?l5PvQ3Cfr|CXid1G6 zwRjh*F!ZO6c);O8&jsD<9HN#lpMee z_PqbP&n%rYNI07GhG-Bo!vw~Ty9JfYBW!06QJd-Vg2ZLN_Bn&%>5gXQ^9elW;LKTR0H@81D_E}&p9{4*gML(B@FM>T^)f%L4<9(8*^JIp6gHKA)ug4aubxBLt1gh zqi(WZ$eGb%1iq$OyWmu1gmq`m@e#TldfD$(L>Z!8u{jh;x;VxQ4-j5Vf8ywy*^j%l z{8x`~5)nJkwzD%(pl(wZpezkW(s7 z;tz>_Ev&@NeY?}-$7X2%b}fct$5ee-1nWB)r8HG3ewdTh$>ppyK=N2oE{vKB2&;|Z zv)qHIgVQP?$YF9daunsu+J8ge0`?MDW})GhqD}Qwm_@d?FCHJhu`H9(0965wbd%gJ z3Ty2QVYB6boek<#Is*^UC5sZ5Gko*YMp7{&KmZ z#{e$Z^T#-XGjo`B+uyc&>0fR2VH=>WCLR0lw)%%43z&J`|FqS9pNBpFYO6!e#~YCB zkX@;4FszX_3X050N)P-Xdwx2oYo!(?LnATo!r0V&(>Zo{(aj9+W05 zvo9;0lUanU?hk&>%rXae0R4HWb$a$H8j$F62diq0{+-vd%EaYCqJJY_v+(A}VrGS) z5wKFj-?X}pO;b&}U_*!-0{4I)QBQKu0{o484oxs+DhZRtLFOd^M)N~;3K^>ID5Q9N z0ZE$}A{EKF<)~knlSs1|7_h{w@ASB(ezWx3z9jA)6kYnr0;E+{N)wx8(5r(-z3{I< z0GF%S=ZvI`Gm7^Qmn&2N;Br-@%*eC=Ty6@0%SCh^5Cd?zKr&<15Y@mb^r4BJIyfD#ud z`?V1lSRHT}v9bB|8e2O8xtn*Wk+={{kc+0V64l>`Y~7!DTrwop7{bmKs&K_ZJ_Z(| z;yRhZo|kTV41*SaZ+an6>C@h}OzRRbdBki=$TbqHVdBZ$L(X|UDf45$0401|fN#wq zjp_0;*HvOY9)$iGe`AGu3x<_9Xh#m<7sB*(FP%cGw!9zw>yrnDytXcFPl(g?qW=DD zgko3gSDC{RgE#4VV?FFOh>m#IbN0Lac69n|z$X2N7vwKEk{Y!FTlK-cr6AqW#Kx*M z%dV710XJ-!Z2Y*wdSH+Us+`~5xeY~KFgy*s7?KgU!_mr3&J<|E6b~%5so#u8g8dj9 zC?1ksi%GquX-t;F0aks9u*KQs&DTh-&p9S<8El~&r&VR8xJ|O)57oj=fm2=dQB|lx zGDkObeHD-vQ9^A^ripOJc&%jxEiyzc>-@$E;zroAs)nja%ybbI2a^740=|#0tLXQ; zgZB5Mdz|N5jB7p$Xk4Z`C2prbGy1L%4brYhLGyFDJK&S{eD=Q1GR4G7+33&8ed+AO zuf!B9gVgBO@bxiTVZ*pVx!}~~xnlhhnP_kuOUEXG$>zZ%^rEVJag;yEOtg_56EB?g zrVE!r-b1epJJQ>MHD41_vd}QuxOF@Q55;+D%}Q&pfXhejJsYT^%?e-6rb{F5TTg2B z^tuEWSj3ZlyrNOvvsd{z9V&neJ>*q#%whHaYl-j5%Zw*NyUd&oWRDXSf{`-3Gn2E; zeL*vB7WISsn92F~k$IamWb}q)4&5K5?p%Ek5lNuT&2j>0Un6s) zA)u8TN|i0=(CW~cc4;MRQu#YlK9kf$mP6a(@qfMCRe+bPI{_ueEU=UvIbJbb0Pu2^ z5`b8NbrXCXhklc`Kmfd4-T&p~(kN#9yuwBPnE~)}eK`B_Z0fzB!Ievh<@g}o-%K!s ze`bDnHr0Qa_}XjN4|HQvC4`&D$fAZ7im79_GLpq6W0on_rRC>`}VQY?5!s z@veQYs7&$L+XhiX0vmzw*r#l`cM%#rp4K73U{StOWM%{dz`n+rVX;TV zD72d~+*evLZTV3jx67-yxg$o3$8@)dWR6UIbva6vh*aUP^G~Up;m~53;mr@yo;s=lM5*FitcH0NKIFL&j`-5s%&O-%jp! zzN8N*t%_@UmSGjCMct(tWE+-#IW`{1O-{jj6FqCOS^kuL$=PM%$scfR`Kf9h=N;^v zI5C;M$0|2S`!mB;PV_UW$wO&B1H|F(6$yH1FgL_ED9j02X{T;_Lnlu3B-qZxU==;y zCWcm@e0@=HS!9L{g>{nABtvs+#JvKm*wBC>anjh0A$fiHd#5;Ec2`Y1S{M`uZKQ~{ zmCcz1=FjP}UROB>-cOGcC7!LFefOo-=skCpfJu`zYZiI4Kyl_Uyj$nq$2=}0N!bIV z26Mz3P^UM@~mXYY|LFkMBxfkICS8CM`P%ml_$21x9KYW z)97N`KK_mzUzS!HyA5kM+H6}TMVDn>gMtz)fKKlP<)|c!RGx)W9|;EfDD_O1;&>kn z+&`65@~kv5g`{^3pH1zO2CgVbo8DTATy@6FvivOHOdwds%4VQsIZ%d8RN4ehWYir$ zv7=ggj$CN9vV(#wC?gafK*EDCB!FiBe}&yuR2|F0E#TlzaF-wpx8UyX?(VRVK!D)x zZo%Dk;qLD4?#?1Wa00pPea^%ApYBV&ca722T{UXX@A@3IZI61ioO)0nDc@ zk$Vi)ap5apnfqZvMT1)X{fcgFN_<0wwk*tU%NG$PBgm2C4#EJh@i@32R_}j!yCHad zJlAI)3`d*psC~bkq>j4lESH+LaJITfa{f+A9N4&7<=$1kvwB+kd`&=&S)bI8hu?5F zcneL&O#!zUy$a)b{xes1#y5ukn!SD`C}Crmdff{z*c3tTVjQyv4d3FbdPDBb(Y1XdHw z(Qf@ndu1ltLSTF1EJYqh1cU59l{UL5K%*r&sU&f{XCYp zN98k|9B18)Y-H1L#Db2aGHSBV69Gxub~g`4)NJTxMYINneL+}8DZr8~+3F7*`3Kk?lBCub=bI8cL6rBsoVih@?UW=ZZ}A8;b-fZ}QQKfe z-_HhlfowT43U=KNV~||ZFZv{6i+%)NyoDaZ&jpx&kqV`<7Kou@#!U4XYbWGO4H({v zaZW8EtyNO>9(Z*yz`Xc*S)-*o!EcV+M8kEAhdHKc4D!w46L)O*@O5{m4m(d{TE8Hu zb!7>$gorD-OHjktJQY0Vy&vuGAB>${FFxO%ZALisFWE)5NJ*^b9ab^NwJV|8Kh8d1 zVjf3_mmcqBe^n}fz;&xGY43r)_#P7bLl8sO<&0|@*UO$+R-R=EZ8t!ENP7b1T;Y)Z zqwK8xgW+hM-ZH1X`w-manK41J$iy;i%H(L zpz`qfTBWCpplle>*9L=7Xij#bRwwW-%A;SSHk`*E#d#*oP~)^M^%I5-ZA>l^CL_KH zPu5GCwvf_C@l0CNU@e55xdJ3D(ke(Y-VRt18U{9u*;n;enC}Y6kPMTdBU`HNp<+U$IkjFgh63TRmIijcf6qd;jj#-R59we^0+5?hd z?t949(~Q`(oGakUc;k?6BNk9a?F)x#y;GHhAXlmrzqn-;RKx6w13Jz!R<(c@1IKI9 z+>=KVXluQ?i$kXgE8K}pu+m`?>BjWMOh23}jt|!(@*>{pi&wP*qQQdZ==~n3E$P_- z)=tx}OF%0+&arDt_D&b)y>{}&SFYQzGtO40i(|XqoOq))nQ6R491rTc&Ef)Efg!?7 zC`oHSF$A#-d)-t!cUgAbsYdC9RX3-YSlxJJe0r*m>1&G|I_H&JqciRECv`i*MxYOV z%SA)+Y8`@vzFEY*0osr`Ry2K8@B5`VprhBbRR!#l`~#S+K3o)OFt`0ro^4l!KknIHhd=j=IqrxqQ#Hq&L_8*dO!Z`HFqEek;CO%= zuUCebJ(9DLZz|OeE#@T3cCs6_yNvKv;+27nQjB`>HVd5Bkm&qZ7Qgu2Ch8`CHuq`7 z5fFphe_UuBYVxOQe%K$Oq7G3^plY;Bau56xJ863$BNaj$Ld(G}0bkrj!-W)=u~>|V zr{~73h_y4Plj0Iux=;`rjBhZTbCGWm%`cj?7m%N82m_b%WL933)o+(}BtRnTb|N@z zt%G7&>07!_azw4yK}3C>A#Itd(8MCb8LhXU3CO9;4AJc$>!c!5*^~_G- z(NW_?P=mwPGeUZo?WbnGyRHX~`FQ0LPCXciY8B9ILexHJvB%@3Q*f8&c8Qwh@DY4B z;%yG_qd1=nPQ!b9qIHSImrewpmShtEh(AbXMeQlm&IWinFyp)hYCdJ-3jC`|cK9lV*XS8g8QC%c)Rer; z8nU|7VxV?^d)-1=&b^1Li@^sdw0I`@R&6(WF642+OsOJ^K;H9==18Zez22(^v<^vo#k7%=-)k)s69|=v>#-$(t_0DIp9h9 z#{+Q(iNF~%WlBwQt^T;rOeP^+54NEd z7|twMUcV5D{Z(h>-c|Z&*I7NrTZ-)cHNyMp&=GQ@lS`~Doe)iQKBnW&UVIAK*A@5R zWT55gcg=Mr+<|*g1M_X_Yu`MNQL*b#_bz9A;26D$Yh8RN0RX_rb6$xVDxw7(O87v6 z6Y6;!Nn>m*TxGQ}o{nguFrZW$6wld-NM z&MKWg{5Ui#LwKCXuRn(niJ$fjc*i^L;Y3-$B&gcr;=53P#|~+Hd@vY0H$Vt}Nfw&| z$j`Pgu1H?qS}b($*No2;j6Fr_O~%P7fhy-?&YHH(-^W8^+a|a*xYiKUw<{iR`G!H({kN%&TVqz_?*}7wMi9!RD=#`%)i$B%70W7 zy?wNh)exm!Q?`&3`#EKuY$~#KF3|pQ56%TwBqe60%Z97ef^*tN;kFM}K+mGP`UpgT z%s3xMfFG*X$1pe$}MchHlv2^ zQ>@+NqTuU%fnM5D76F6jC6zjUFr}rmOoU9pUxv# zCSU8TTd&!bQ7*1b&{UYKI`}`U=T(MaHex9%tL1PcHW2svr}Ry@(~`3-!@`)`ws3y} zj-g&?&u$S6h}8!*H6}Va304kFR(|}!6dL%>Sipd!@>G)H4PL4ZNNlk*)RV>W<-oC(c>EH!8{dHO_VGYg!ArcY~Z9_~(D%kvP9v{!(zP7{h*|h`H5S z+i?zd{M8i(HIFVaCvem3jCOQ+bK1_b6HHk|U>N}k!0GIYH-x*mqTvmjmcSquCT{&y z#0fbZ10u10m*Ivn#BA~>oP@Hw`i!Fh{wNl>K37rg3wUtAW&3+)j-%Krr;x)hbOS>F zIqBPd`aX&E+!SPfuAf-o1YKwBM45fX9*)EItMABhXh)PzD1!ltwUQ!YKG%j|zQuoE z(xm@bD*hH|*wn{w`BwB1w__pAg6%9g4AWNl<^yKls$9B6l=hN~dWNvWlMqJJ1YY`; z#;v8+93n9uC1WN1@!swV-uXh&tGs$&2ge#zt;?jY=x(8p-MN7FOB+UR(;vO zFi4yndxe9wv|b)QEzFbmPS0Za#&?qw#!DvJHMUh+_)p3Bs@pHvUlVgVP!>zRa|A3e zs2t3UR0ZBJ5Sgu2Emzzdyi-C6{Kn<_vdm;MOK(i99$w_;ebw=M3A}2Ak&fxxkTE(7 z`Ft#h5BKo1M|`^NafHj{31z3g#)cxDSbPRbJ!0a-7XAN3?wA&`XHj|SqD9Bd<~J!7 z5`X&kbx&!rZBMeD;CCjr!^DM_&iwsDzYL4P(W96n*Rv2fVv>e4vW`ltmBj7w62xji z$)6845xq@{r$@KR8ySph?Ts`-V*fd>+93dXDya8EA zHstmYk7SABUEKx)5OQftO@jR*SfA^-86hH6@Jwox4Z$IHju`FY8<5B!mM#es4X;ln zjt2AcR(FRJEZX?j80MR0s5$h`6J}gRpRz@NEW(g`g&`-q%mQ*BOq>>aSr%;xoTdT> z(=aIE{a-{~y7I|O{9#4fV~h>&R*#wF{bxmE)OF9EiBh@LmwvlZe zu@YE_0eHF^Cn8L$LjE>LF&*O0=F2Q|rr%4%f3%5f4Zu??k&B+dLj$^ZXGm;LnA>UL zv*+Vwq)XK#yr>~drIEhaoMXcy^A@b&o%oOIlV&lcL{4L2uKBl#e!>)OCXly7stf>+P$C(t zDVrnt$+A@+pW@XMQMmSc{w!_sQE?A5pP{T4Jt{)B`A#FK&v?CfnzO3hG|+ijkTWyj zrF^!NPyJs;-3VR7*Q$4B73eU@@=}i9lJ1Aiw~c(PVygG7Y%)o+0R1)JDfxb&6J-;c zZWWqKMI_`aFX-ebU4VaDkI>awNX@aiH~kH2bt&wa!Zx%qRHCD|$$9vF5b8c0v}WLs zsDbYJQBWb3a+F63KVcGlYdM;=N=4l&+YVOU4baBBr*((V{z?aM$E(w>sC^VD{Yz8L zbhVMwS9ZgL{t!J+_MEO7wERA=!w98HBZ~J=KSiyudCmiqzM`@4DJ|@O^>pJjfi1qu zkbz-rLuOLzS>aO!4kWr8frn7=U&#Sb>y08tQX8MejZ|vA=I}vS(d86=SpW+{hAi#i zu^4YQV0cE!?7taRxPLOL6&;m0_y3esjK_bJRB)QZl`L;U2q{dFvV%b!VR=VbQxuI$ zDe3appM|CZZxGGjGWJRgAN{-<@WgOe|NIpuy`;6_w9ZCvwUVu5E;dqEDH<}%9W}*e znnUi{O8S|>bf6BP=P*g9iaivKQS98#S2nmKKk2M+T;Sr9w|L7T6)JAiUgm*Ny)%D2 z#C}xF^0SCVVta%N0~ze{{U$@FoR#VC!Sq89lhfBPfRNq)k+~c@Xs4pYSjM`P`HeUD zMG9IN@xx9}JDB-~qPvjtn>|6>T4+aQUikF|7}D`1Y82)L4|Vdl*i_Y8moI`Y6q0g7 zS6LB)2X^r(oIzMGTxo^b^9(3!i1_~L&u+N-x6Ot!b-PfWzEukohcQ+2lHb;x$TdS^ zNzPOGc2|6hzCZ3BI-p|zQPX?xzuW_K4?om&_YXDw>0dS703WI_X8S`;zv=i;(>4B4 z)3FuQHYGpQbYnUyepG`-5rHMER7m$u$%8V$+4F5STOy-T&F?8c;U)z8a~l5`Lpbhx zSW62eJV|McfbE>f2qu8w)atkMNPxzKydJxNOp_@nK`DyFN8=@7<3TRpT%@&ZvlX6h zI)RBNWDGB^%bcL1&vt<)KYES{wE_ICB|(B(+a>UShf4w8y&nFnOv`ECDvVxY8fpne z(X@%D9VgqMz>l)$Gr78LFmvZ$a-J*I{^b3kE6?}y=C6nr*O)Ee|8!g)+T4^%A-g0f zljT_ss(H=L9SJfmDn>M9+~@{{Cs5J`NHnT}bXV>*)-}(S_g#j=zBokliHePOGw?*q z<sP4IiG7s@fFI(g#702S*iEJhSG@<0B2-eAjCyql}f&5%5TiUWuKF8TL)y5CW7g z+Qr202aQcFc1`z)Q~zKdX3^socOaB zsEeWeYV8ezCqH0I=}FIJu`yH+fNc@&gTh&JEq~DUtVe#hS9CcLR)3yCFn_SZQq9nm zX(@|Aa_&GNxKVCbUtVI|<9nt%TepTg5K@8D2FV+=4Q_^Gqle$3Vrek-DVoR{C!EG&%RfAX?f8+XIrVP0?EH2AfAk@MC?yPsKtSfv<@=3Hj!;S=_ox;B~t#uS>39FUw(4t#zTnO$X`oP@}7B@_Bp`iQ1tOm># zW{QBY$Ffe(2f^r)V9q+9MG+~(q0j~r-!~{XYQAvc>}wP5(;S;d+*)T*+CzO&iD3gy zt_`zks4%yyemrfkgZu|o3fzX%ut}9|=m4t_M0MndK zz8?2;G(F=uxdA{KRE80YGbYSk{b{6^`|sFHg>7M(~@r=mJAsnxW zJmD6vOJVmynBT5;1)$k&8}D|MRW{-c-S32mkLov;LGt2ldf5^7lon)i{<*qWcq$Ez zRLHn=x)rGizMjwkkkJ`@2Va9aM~3dMH}<(e+3oXIZK)Rr?C$7|#U-r~&SD)`9;$LB zT+r#Lss|%8kr14Rixx<4`eSZMRSX{7`lZHs`0i)GgRgy zV_*B^L?fUG1c&L>u8$RO=BE!a^`l<;`*k@zowZ^CC)Dmo&qvQ`>6cFc+Ko@?G8n+= z>+$U1vdS+yl{>M8o$9a3m|;nn78^jk>23yb?bE5yJT6@&0V5ZN>k9e&=iP-9IH@6dP`)mh? z!d6Hb>UnpRp6g8DUMP_oO=IClJp%J;>gi*u~xOBl8uyq6R6<3^EjKQnh}cUC!4&PF(&hH z`KMB)hOwkvyhWa&##n=T%ptZ&q7Tb*NK4CFO3Ha>L1b++I3SH@L3f^JC8jjWk6SdY zvPC*y=P5y-FERefrKcRhU&agajvlWFa&87OgXOkJx{X3cPxjGLx_<1BnEGIEJ`L+I z8|7N>vuqRXlB2pG_tsT$KW_vf;*o#ccctW5)LhB##o&jJ8|jdvJfBxHvtGR1Yqxzi z7n@rk5?b(UySnC9*A&$uW&EC2O+jVNp$wW;lXC{93_770wN`7}t3jtkZ6LD%{_o9e zQoX-5mGbq1lt;Yv>I5P|w3KWpw2jpI)_oH=opyZ_M*?0S?7Jb)$kItJloM;wdM~2*^6Vb|RyhaAcTK412NVxY?gM zG)HJ;T5yHUIHjbqv$sWCpmZ22Uou)v?eTy$DnqZWsHW({grayr?+bp2`qTYvrV@Js zWjm!uzC=K0mZSrMToz4Mp#+BG-DWS*PkX094W~hbT-g_?i~bcpCS{WHH(q*{u9_R9 zxmtj~ggQ1UT{C=##1*~cWaK$cn~eWny$Vnu{svCF2=xa=K8kb!!zz_mU&jsktG_m( zw_;DRV=tNll%dM4Se2yLl27UorgnAR-)uokqwYlJMl4TapWCEufzPsV?wB2d55hx9 z?ivB=iVKHx<8)vSbhwA> zb*eQkI{wJOdLA=72#d7_i`4kL&i2Qz-?aUj)@m{HlR1=mnzAJqLpscSH78pAxPZlT zvQ4@ZyvOA|(OnW_zKwpWz9{Pu*lx)Ffje+1pYBDYIdEvMS zT&Do+9vtgW^5btB89`%$jzL^gi=UQ^692-FrLG9Vtf%K_Z;+d zK+SQT@g-uAE-*-}SFKqd?{=2yvYYfOx_TPOiu^qBZVu#ejwT^m@L(}YLq9+jQgWI^ z!^6~vS>$iKK?(0y;LgO5^nSmnVPTXN`ggnKU&AToFqo^os1()15r&*R(fG4T8L5Bk z4%x8e?;eE{%#1J4SU-JODsoh#RNYb;WHR0S^&2Q;=Ze6W{#yD1z19BW%|Qvx(!S@u zz_CMQ&4CMR34^j~pI?W&DrDdag(wBB$wQa{c)G%*+7Puj()h_I)=kuU;^pXxN1DjK zyYOQ%hb^As9tDdpq}-`4bHAi6Uu#{-QeNxU=grb|a*8=e)e3M1SNB4q1lctJ98s-+ zX-tJ@ES=4*8!`J1*lg2%EfohKiCqFIMwuNqN(al>0#jV}&~D=J&1ly$)Nw{sGcX7D z3M2wUeDD7bsBqH8T(x%AP*H+q7T?^dsDj+gnWG72SRG~d%uhYLh_c!5d!y_%;>ofA zt4%uwH$e9Ro^-eQH~I0@Ah_wQTGWmceb<)QZ8)QmB?~EkI|5Y4zC36g3T5|2_kZPam;@I=?$6S(G_F zd^=Qf9==J$Z*`SdGzSie+)&b|#8HHSWldrYx!csN?NU*}^LsZ;#K)EKJ_b?@%8>vZ z?5Xip5Hqi{!AN~~+Kjeaom1vV?bn+IUu&(bb)~^uV`Z+}{Q}50aY;!zYl_)-y?C;j z*&^j(_2Yq-Iimi&x-mH~|MoxMiD{a{o-H7&oyZ?)Cd!yWN2!Uy(QMocQT=ZBMTd=p zx?!U9wbfgfy+o#4Ojm~SfpfjCO4mHm7F3sl2(x~)Y`|Q(dcT5F-IJ1Gmz*lVu-duP93JP~+^<0V!|Ihf>X5G#c-VZ#ims8L z{E5LLd8y>9tU9B1GR9mpSdWya#*dcXOcV&myXh_w&xda@;Hk$%pxyhUn4#Im2$a&LXQqK;W&1Xjv{UBthWe4J WX%}N*>t*_1 literal 491520 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%0A8xyE}9zF1v-?59bJ@^ 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 z⪼ZXR>oiWe(s>NbHlh;Md*=3R}tk;Y^~3rJrKmxyqoXtN-ClZVa6;{%|G{%Kzd_ zs{ZCorvGpzkbiL|%RA#nB#0io(p6s>jHGfIz>oTEqRkX3hbXsWi)f2fdSr2mdh#pr zTU|`{HeV#?*f@z^=k+aDUw&tzsm?iw4+({NR&EYkS*ffQmWLYVD34n^#=S~taiKbGPM-)si3Y97p8CCH zK<*MM(f+3ZG@W&e%@CHS{30qC}b{YV)i%qSka96<*9L<-5P-A-3<+1 zO^GI0@Z?ro`{|GLPR zN+?M|$4A}#*dDzWFQ+wFmr_x;kOGch5(|41hI#in}NB;yJIXReJ^_Q9J zVneSU1?FC?A-fSnh}O1Ktp4fEDM$b1Rh~cZ1#k??Z!ZOk76G~iD!tLyddMJZ71Xbw zeUI1G5CpQ}iGxPGpEEBf%B)O?|E z0NPg8T4^Bi&C=(lo~#+wpJ-vEFOz zRJwZ0Q1x&pth!*NCO7e2<}RX{EEEdR_yIVQe&H2n$#bK@-Y0>+o84$AB)zI>qjsYF z;KMWSH&>9Jh7KQkg-|ITQ0%=Rj$5k}`CQ~4VIQKJs~WmY^6jfMTTEBTWbs%eaK6r$ zyK5*R4}0eBfL6H9_}Cg@O5<7BzV9iH{VA$iy+VbKJ-(@v;9IaOS8$b1INB3-=dswL zMBjJaP#Mp8;{3h| z?C&IR=;@7F2z*=bfCw}+$I!}+Ukdu&W9&PE83BIMQ6OmelhyP1D=5huK`=0vp|24O zC)M9hGTvXp4rX8h|LG)?m-+zzFsDcipdbnKpYz#kb|JpsDk?imY|*Yb^$ku+Zu^A# z9#pDO5zI)BAH7?oE6eYm(r!j7?T;nScW;8;Ngq^-&kPev>Vqu`c-w{BnVpA6QfZYu zmi|JAeKxb0kew4##NM~Sd+A$GdHGeBGoNX9dwM9j4(tJvy?Jfg+96_L>>ie zybuZK+1>uDXXkqHLf)+6^jFU=4#;P`4)->?7|hknri{QBiL8cMEzUAYRd>leQsT9vOL)y1(Z1aitFe}Sd zh^qPdCjaR5UT@d+z5=j)GDFdgd>`G*@vK>dh@cDaHE@whh6@)=O+E!#D!D>jm=+0h z7^TQ+7u{AUqaLzh|KD!fO{6uTBBR@ECu;Pju0)x6%=BS%js$rQw^v-DK{7QRpA>At zkkaCh2K@U8T%ccfld-vgus|A0w2hX&RoyaS>5e650WVFG46NkHi|FtM6GwmS}@a>FXme7kxEu<^X3=&+_WRp-A zalfw&PS!E6Qj$E;hfPc-*2W=6dm&?OkUo7gi#YQ`V|sv5&7NOrf;2xiZyrr3wBouP zwWbz$5Nn_PaKJ0fB+gVJhQ}^pn2@eJx3%zKld*yn*CTD!6JgwFy_rIQE`AAQwyNf7 z!YZ`yOn`UQ=i*l!GDtKLRzlJ6S3<1{?x=t@l9Z-FFRdGxkpl-~+bw06Tb<7Fg=VTB z{X{}*nXz6iP-?+{(H!3Kzqg?ZJffv(;t(NxB}cRGXdXQ9HN@`??M~)8@BJKgBc&KM z_v8>(i_K3W{PC>2x+`ft7MP(T)(m(nxfSs8{`}Z~x3y9IcHeLd#Mk|ebLgeISgpbA zfFGt8+yS~s5C(vI;M#%1yPMG((8YBd2%~+BYl?$Ur_19rfJyajtvd0nH z_w27&Ts^4mAFX1qISTS^@Q>Q$r_LKL)+;il6gI8Bytk@v##C-0Jy8P=V+*1Y7x=0? zvq@6Fg~0JDiv;*CGAa@M{A`4WRz}y#V*)e*jxU|CFAW?j(01DP_3Fjh)rf?a9W5fH zP|K#9UkAS<>4`g@c5^0ZU$c9>=c~`VCao!A)X&oDwcV3C1E?f!(nN z@W`QN@IseBOwzSu>3AVcLP|Jr|iUK$$Xno4x^u~{%9_y|I%E@&=HM^)o$KX zZ&RAS#i173lKwp<*Wwjwh7MUC;T!!9HAHjcrF94wcE>>ZA`m_nbV!W5D^mluMn@gz z$C5+a`4K$e#aQ)}s2mtYuZ> zJ5Rhi>u6A$8E4iq1anqUW+M|LxpM(xXY;9kkekVHMBA^;fd2r)i+|Ve@g^k-0NIZ zZR<+)0s$ zyq5>%%XCfg}amR-sZ5V}E{XnpnXgW2r(b zJY^?fDmb~yJpI#Q1*wden&VnG@D)EK?L2R1! z*OnYktuI+*;dE@fcm(D3Dh)8qT0STMhFK`UFsmd@zm~fUTb^M+o3g9hk1(3i`^omNkcndcpZGqqqwXlSwugV#@cCJz^$170O8PptJoET zE3@)DQ&_wBVf46?T;J_ThO#bh|I{e`AuCu!$GK05P}1R^#pb0!vBDDWURHV`%Pkuh zi_8SU^YJOt1}-snFaiu(G7zmG!%B);9_IwX5lc8?XB?G^gSQo7NOwd51!e!N*A0EG zgDdS5J(%GO_xOD1w`9Nios=6_r;>Rb!B8iO8m{*oF+f?ab9=B$0NNyTPNWk9bu z6T8Sh5A}yc`0&d<9KgQe{^q+JM7n_~H9aLLi4I(ohrqJCb#|5i(aZB0|*NTR(Lzl`o;Y*1FR5 zs#ANKbEcamR}`H=iqaS`rwC}U4gHdNJs;Ww6A0s?f}V$&M!CrDUNsTt-NrCxuDd-m zi5i0Tp~F}XKkGjmG)@D@s1$7D@GEX) zh(vmAF`)p1A&s&23rhyW@P`W#5?u$&1KxnvMXuKj$))$knEX?J*_h@y$3pL-ZSAj4 zHpif7z-UbGb>#MUy5xC(FpP1I$*!#yc(A)XhW>apj5)*N+XrY6_5#N@A9sghq7hAN zgcb|%;ZL@N__v5(S%?zwcRwND=m4`CKLR->rBk;RDj8kKpzO(5d0u27g5YubrEHBi z+BV*->~oUDEkGC%w`IAlH+2k!{TACmg65N_7 zyWKf}X|>Tp{TrX>PS>p1=n|9;MO+fMpWBdQ5`RF1UU*A}Xg(hEH!_WB$C)zec}w!C zd`;JcPc=w4j7r3aYJ`1i#t>3Bj0Kv65@2Ad5yg8=WU~YoZG7!hXw7^XK5NVXWvKQ* z?gMk1aPB1VO3^H`Kq$`fgv1^SoT5x=;+f2VudJg9PMaaZbA{j2N>wT4kFTMeL$mj@ zK9>u=#yxf>Fw)8yZyqy!vgamX#|t1NmO^%<5hPX7DCRrYemg;SR0U-oUG)}bwj1jG=96wRu|4noa2wgbj z81wD9R^nBt>!cZd7*PYS*9VmSjrjdc@CQif#5@J7ZrZHVnVOVdBpZ%dWQ`<#)JP(< z;gSM5ezH^wc_6v8sp1_OgAzH*|0z3okKJ5pvTryhrVe9L`>Cl{cV|HL{-axVZMY~T zDGqQV?T(jZF|R+-)X5Pf*Tg*ETm5%Q%XdF6TJ;u7ObXbxUl#_9q$^}UhNtxklz8im zB*a9@J(CE$oMt3lhrRv9bEy%7PqxZm{myng8JzQ)(P(9ikzW@f59qUA5bWb5_H!p1 z?NsoY(iyp*(~B?tYM@+`H`2=n%X}VC?!q@|bgm6pzGtley&sO>t9BgI%9)p;DrP+d zj~p1y9tld)eM%>wcxa(nW_i4ylxU^8j!c=~poSUMa78^pDmH>1DXQF{b-~0QX^di~ zJssKi#i1%VCT#_r`tN>L0<(8uR_fuu`dPGBfPNOZLM`6R>E#h(dlmg6Q2VMYl&Vd( z$nQ3WJBY|B*WTejOLL*2F1@gj9D!pj^O4AYC0M=Uu!Xk<)*qj%U>g6+)XZgNwAE0q$%%cH z<}kI;h_+<=Z7FIHGT*S*{^@xiNMXLUE-6{H#Z!J`aiaAumiCublD^+Wr=!(C?N-uY z976M^V)ED_uZxPWVA%xoHqD2N7qcf7{4?G9D-s0>EN@3l2M)B~ZHZwxdd;rf8aW8! zwR}9Fu^0aBF_hl>1`yEtFkjY800gwuGXVju5^E9n0n->a43t1XK&veS5YTEz6EYm7 z{5zo4)AA>vip^qvtzD7%@Xe1}=Dt+23U=Y+k7&K?a$ zoUXxtSl~iAbU7tXdAa2+hYMBg(!+vJAN%&Qqz&<)TsUk%66@9F?cS2J3qXXP))=CC zn|O40zZ^N)`pJ1A+5#Zioz;iqDopdo4^Q(`Lqac|4$tSgh=dk{X+@7}9E_IADmZm$_Y-6RwK{kPu!M#)P%bm5pU4!2~&2F7kl z{SnU;gN=DpUu++bv>P^Zh_tsGn}-=(K0&_!$aa(X?~xe;=RZg09(<7sDZRd)9bQ4+ zunuh3&s>SxsdukxNO0~u<&w{w(jepZ?w$D^6#?ioycswWvQ8!{)x9SZ{wShOhq$It z_T2)CkFOb_jCDXs2y)NQ@y29D2kk_Cpo3Ns>-B+8^uPoagp{ftiV92{9xe`mK-rxu zCOnqvGj9e-_|R7u5+p;+Z<4;?*U^mAR3$xGA&EWPG|)3`bivn~#eucLwEBL=4 zn8Fka1GVT_fYq#=%qBg7!K(BQoe{owB+NxWHuudX%mG{M#U#{s`8;_ZzS}>Y%HsY0Ta^4yBBL|A{y<7zq6gJMLE{vC(kc{jRS1L*dCRk??rxFkz%``&vsC zuNCT~6vIu6MpZN(#0=I+jzR``nh^nvHek)NGRXb?@sf<=+ubGaQiZ7g|4h|{v{$Fv z9q&%EbcUHZYNf~VaA@DK7WW~add!#E8dM}iu0%BQXRY_$!!3FA5{(yZRTTRLOX^V^ zkx%>7XjJ>3n6o1#k~H->_L*byrX}H^9{kaZwhc2jg};ttRH<@cD_tF|)Du0kYiM-= z1z@>*{Xbm2Q+OuPx^NrY9ox1#?pPh$wr$(CI<{?nVaK*@TPMBN{?9(=zpC5$)J4^- zddD2&K%~V{7;&+D0J6GjsFZ%#=d``qM1aTzq>D?Q3!W;=FDTJG^{2Y1t_vghzDNqW z-(j!5F~9L7Y&9}*uQFL$3yBb>+ZS4xTE>7^h%jG(6fr5nqy@!30$|*n5z>TPJ$h$| zLFtNI3nYd&`E}qYMLj`pN!_&?9ma@;tQQ>K`i0Qpq-jyv3`u{9XS47k?s5&!~wd0g6Yg{Q@U*a z9>f;Ex+~u^5bW56%}rA6&YEj+AantLqbNLS+IH9?pSG3v+^I6Pr#iPY7UY^)KQ3U{ z=i^2G=JCV2m0)d!; zL#SvSbj@k2UIcN|uX~Z=p^MN?7dq`xD-l@y+sWtV=Ja;r;ALe2+^?7~xAY=4X#7{+ z(}lm#kc&R+1gPtpk$=A} zyu<{kg}k~ygO`0(Wh~|lV*Tg=k=D}X%3IQRO)gclh-r5kVhMG_t~-=lXU`$0%)dln z_WjW^r8PaM-pBxd3)jzW@_DiY;VR&bQs;FOyaa3?T>PCJ14<|pJzo=IyPqm0tW-as zVn4(hn_mc_L)0v;TcEYnOZ+<9&38SRqdd_ze&db_8pA9p9AnCSZ4gkYD`&YFhOrb= zFAp9_ci??<(zu}se_&iPO)1C>PkJ*!5j0miTyXolRta|&PP0_-Q6Wk|K`^__tFfZh zacngiFR|A)=ex2MFU6?8&9gHdAi|H;FoTo+I69s?JdResQ;;O4j341m=QzzV{k)`tJ@gb8Y>GlYY z$CUvA@~4yWqJ~M>wx;63Xv>CJ*e4SL8Vrm+CD5Oy1Y*_n54zbM4Y4$-rUXA)mh@;! z2kVjI7!rP==2*A35BQsosZJ&=Gi=Pbxe9J3h0!3_;@qSVt~~7+{V~GC*@9P z9M{81+h78h=G3iv9))nCN(%do2qt{c%=XVdhE$ZjEGRJLCdjW)@}NQykv^A=q@2j5$TgD@z|ZZTvPgiuOdek zzw;;A+l}^)QT{0t9%+5rbFNYNXeLbT$-I6$L)w?NIZD<7VI}^B_zCrcr557T2yZg} z+p{YolfnGy`pm-eJV0QKO}A8Q2ajeU9VzFDd$}Xb`3yy^Li#Yg&GInL8J}QlOx_)q zm3CbZ!x{6P*pf@$Gvmdp#ajJ5zCBfcN?uAvJaF=Luw#WQ<7I6mYqT!PQ9p&oNN!Q6 zN%p9kpZmip6@C}ISwc+6w4VV;gA>(3S)bdZ;OM0i-@jc1^t4%xXk&u5(v$yZppE`2 zj2bJXgR!F~d62yz0r5T1ehT}42ihaV4A-eLn=#LV6C(s~aP2!Z&K$)VtPSN;k+J<$oMmed3i zz-EYW!17<;@D23I4ZQvG7Yv0g=4>R_K8<~&q&wZyw$WCaX>|Q( zrhu#=$ArpfvQgefqfVbT!R+x`9Tteu&a8&QciB2EwI2e&Qm7eGs=|fSYb}#}T7t#q zS6dZe%=wc7DQ-Jf@qBKmTEc$cz63%Z)H@{b<=cZbaD4wF1TEVD{4Jk%yO{0HcKmID z4X`b74f(u8VyfVsk`94^DOL4;=0fE@xil5+@4pg6DEno=E2i(9<>&w3qyp3Apz@wyLTX%XVG*S$&6wSmLr%oA9NZ%%XX5b zBtI09^`{ryj}4hd{lzd1l7Io^m*jBuQ-mWu5ch|Yj{Fp0l@7>6LO`AHlS^1gn4}l! zx8d^6MU4;`GvmD#`bD8=fT^dEIlKv6F+Y#~ClOi27d%6m<*aC(;04 z_5)Q{soKm|Dn^+6c47C;B^90R^T!Q^RTwd5^X+MWwR>Inz4ZQcJ0WwuvEcIG|A2<0 z9OFYlg6}2Sd+d@W(s>1=!2vEj=Oa3g;(khDVWE*^+6)>J)|)2Vl36*u_*P-%osDg# z0pw;*M(D-^hiP~u`FWT{=jmRbtgSDTT+$fyp5CB2IAzRwD3(d$?BKG9m*jexBpC?b zd#ZYzkVgU};vhC^4%9>WH$%=GESKCgByCBHYzs^5GKK`oFy5w+8;z!D4>rW_yGY}+ z+a{i2!~6&AEa7(aXMyVxRZliRm-I2Cu8e$#{VYwBw{arng|_-bojbFDWNNOR+iCq_ zPyBJ}1ZzPmDOct1=iIKc$}%eX0-oUFrF=ArxF5#8N8N1d7R(<;nJ|5~=;I`UA4ylH z4EAWOL0U+R!P@`PBqTEs_sK(5w)9>-Q|+Xq%4NkE3Y)s&)2G_X#x&$k(lc+2LTAeNZvu${pq5|C?Pfk+k#N{h8a|Vv_z-UT}9VbI;!J|u|F1v-< zIiYdTU+hq%`{msnK`DRUpQq*CnKd}HQ$|<9moga2@q%}k$D$H=+VQ3c>Z2Obv&p9N zA>S^o9=Q-YdSdY2QjW<8QQyR$C+PE)pkm!-kg z*qMe2eGLv|qz_u+m*=G!BZI7N?X?(~Vl0KgUx5=X{Ms_}zfd&7@Fr3dSxoOmmhh`_ zm<-PHjE!6D9@;o%qp%ggQ3V|tw5-PDS9ATKjsbDp<-s+ZY@u~nt3>;y#_w=Iiz2>I zQug{gVksBsZk55h(c=5rVLdjf>7tX*|L<~{3t{O_69RT!t;6)^N|9H+TYs0`w7s>B z{A5OkqSXiSW>sU#Rse;Day@60Yu(PNc?f=4Z?KXd6WpylMEhBHCE9cSdgdoZ zraWN5L1nsy(2H-r$%#R0&Ed3qKcATs5PLtqR`fGNY0ccvMvZ=AN1=tB@&@)(qDj0 z-0hSXdvN8L+_&worEmfmP57Pjyq<}EhUOk*!rlIyONe6*a~#C!X*a(JD>iLm`kl!3*;)33$jH8)B8OiM8J1O+Kg$!i(>;hc?qS(|(+HpXaz+~r+G z;C5R96j7}D3%EwB%MdR~st+-2Pz7!4z7-y7BqYo+YD2gcZe^1tt4Jsmv*u}(4zYqS zooTJYd6jl`>@Wwtt~ss7Hc4sdA+w#*WZ5G{wF^j-_*}+x@A(@F)EL42M8Y`1v(uDG z|G635QGzoH#fgW*1^XuQlz%Z-DU(z%%nIbFh4Xl-H>bufAhBmN#=$WwZ~ z?8OK&?b>y-dE=NI?KBWftmamiFDiwJaKbf|gyT%1HiR(8SYyW<6h0Wnh?|f4MA}n% z*6iZKcuwE>6-3CGN7T)oAEV|NfDTcML~g$Qr=F3s$|27f{Le1d+Q<~etQ z&cS>cx7#~DVVs5QugtRNc5~ZcId!FIrROuDGG*??I5EwpDwC{OUDe7c(AJpYTk=Rb z9!#h_^oR}t8WrIA)}k^v&HPoekDBV(WQRwRDy)YaXJ zkV!sSUyIR1vNy|S)5>g{4l<#|es|$iOq2}>7dgO^H(wlTf4lCT`-e5unEuoR z_?sj#5z0gHJmO#T;(v{QxuQ&Q|FR7E zDL!cy1SSdCmg-BeCSu`u0ZY0Xt;vyFHdVy(z)ETyZ8gw3xw0j8*D=I}oEV;W(BSwkrOGMhcxtsO{MaB_oAH{R$S{88iS!6_L z9HOi!L-!XR4`mKh8T$OFn1GL2H9v+N>HO}$uhKH*%@A_HdoicLujj1oy5)z{NJsAN zO$Q^jF-I^p%*!I5S$pi~|5skyjRD%AwR`(uFw>oZbNHC=-K>@Yfz!T+F|VzNqVlRY&?A8k^tC zlO=j-@2cP}bx9wa=Lz|8U2KoBkLfbp7A*dkU_Ym1`W=mDju01vh2zPU3-16F^`S;s zI0+L0%%A}7jT3M5Vd4pBmDu#Hc9za0`W#Z0=FUFx6MYVKc zy#%J)*ftJ_Po|^f<6AhOEN3T^nJjoJT?}|E^Fu&9oY%BjAglHvvB5WO(g6RL$oR?| zW^Z=GuFSc_KA_DG7~oGY%Iw~zAYS|-{;ixj&KT+wKF0?M%8kD46*As6LXHVl9a9dI zAf?HHYZgt6G;YPLO6@c7H(H^ULWy$DlsDtM%owyMi}+b57k~6=idF&~APP6e9zhO} z;gQ)Jq&~UsPn6v1_}nwK}(Xr zd&!f(#MoG<`O<)%6ro9}1Ma1#|02T#`J2Jgp$9~ik(KZD_?pF_xg}}a7qN}@K&*zG zW3LA%h}-_)hDl$fQU83C%FG^GmS(XaI9g+d!W`j%PA{aAnC@A|T-3oK!31%)c)>r5 zZvVYkVi~iGjnib0rbdM^^Q#E4gdIFc7xUbj_YOCEbW=8*IQtPI{dwaH*wdH# z!u#*HFogSU=Rc!B6Wp-A69Jdf`{{^*v(7yT`e{FP5A``x14uVZCmlOPr|j+m3iPia zkOFnCA}5OIGw8@Ffn3)^prohlt>8UdqaCQe8)yBFFm(+X9rs}GF)R26MwE7=ZNujx z9{dgVS_mZKWaM?}&xQ!{5k6RxPi*@&TX0-~z-el&kXHeVERc+{_7nEMt$3~K<|Zbr z6h?xJQ!Pp5h<_jye!c4}`PHrqLPZ!bJ<$e9;uM25V$Hzu=m;Zn3LgJPP@iNG;5SS| z6brQ~6OpY-t|dL6-NvWahnSSRLm1iN!u0*IdIH;{W=2;1bHq1+5b1X&Zylcgrp=HEhr#8MW~)lFeZ|^euo4(Qm~D2oZf0`nZ4q zK+)|UT$;Kc$2i9jKgq!B_JSKoDAd17$y%Trk*quIPBv~BV$m2{y=1Bns1**l_h`~+ zbp^9#g}0*2;LM8=K8JNWEujxQDADTf#wZ#!O{HU+AEZzF6$SCnCatKA$ZRxsb+#jbeW4)wql4Nca!+KlCgt0lxs_PZim{SLxj7B$!BK>X+ZnY zgFe$l9d~~Bu(@@AZ;m6Y3JlJ0`9xvydRR41R_XEJoSxI7ot~9g=8G~ehq$~*mkL{p zgFXEIc2_P~3+n8)RW&kDT<9;&lW#`u7mUA{h5^JPsW8%4cMAO&@`Jvp(sS-s1ysU= z2_1aU`ZfZL1$4B;oIXRzi06!7bN}HnaNXG!xNGxZ|0*S_q0&c7wJfN5sdI3u)nx~MZHM%#l;nrbv`@ku!Ts8l;v)3FGzib2cP_>A)s*7wJDLR^@80X{ z%8h;ni@0ZF8_@^%$Pk~AE`-sx(EFDk!A;y7pWY@&!o9#X{430T(xw@O;9bBQ5#&(Z zBN3NliiDXKDMH*Y2WZh@pkaf(P*V6*+-R5O*6j-~w(x4?)u^jy6t~W2sS9f9k{#(J zsG7Dq{=5|!X+ufachb^8Q8loVUmxMo+DD85aj@%>#Th`8MBwIFo1tT*?K!i0QWyD{ zdJtb|f_$Jn&*IWxbuouq#d(s*j)knkiXjJB%A~!zd*JRc7rT4KOMR$Je^#MDRz<#> z7!a&L3!&G4(9Rr}(`NAgCJi})9|uFpxFrJjFon+QH`D5JPcs!`5Fr7=CSN#OVux{S ze-kk((DKVN<}w59+hyA0T;gE?cN%Gx0QI~O*%AQ2k5l{YvuKKCs)FrJ1+@Kj zj55m74gVO-VX*mq&I7vh$g;TCBc3^dF0!&3Rk0tEAE<=Ax+IAiovTz5@_KS(p*ppT zs4#Ova-xjlWli6N`>Ld<5_(Pi9G;ru#oZQyD1Hd=jecyw=SNslPUdz7DILHdHAi^PZli6w5(6QL!^89V`3mDdUimsPHzi1S}}F z`wUzbhglD;QCOZOq!QmdyLn>_Z#C-|Oc%H47f%<@fWBITW)#xFo#@h(K~FQ!q0^~k zL^(y)(X0hOYWI5~i*|TtVd(3^j1J#eg@_+;Wyln~Tu@@h?j;xg{(WT?flcQjDoKo+ zbb8oI`A_{&RceCMa9_)s!`Uko>!JWpcgMCHS+#Ug5L@*mx$N{qmSRT%2;04|!O{Jl zsEGfmSJ%Ob=pS$c>u@bZxK)uKV8ZMHM+m-gP+0ttUB$L#UPKm?+S14K%5Yc=V)cws zpSGWx!4Qg`^_9-R{pB?BWhVga9QK}Aeyo?(icj^tYfMXIXv-~x+;k=x*8dowbxzXp zYPe_^VK0CQ{vnSB>cm2=B!c@P*iP@#yh|KcPRl-5L){TYP1##Czh)qXT$0Mhyow7xE2s|ug=;NJ}3nKjEvx*CA>HD;rPc> z3K2+aS5j-1__thW7MtO=vvu3A?FEDe97jhShb?C+TRo#84zTJ=`fcLiVDT_e_xf?H z_bk9~Y-o)Vu#5M`UD9=B1sC9U00P0l;tt?Nx)eB-sJf7aNr1Y`&7b!2j?uvCzI}_|nX(Of>J9Wxk*CeIlaXWnEwfjv&J#j0 z&zhb+cGdtCX`47QGY+~c}O+z=S z8Q1FT9_xN?3Vb)L@};(eBFFsF3XL)N-u!gx=&9kd}4N6mss=X;i@+}Pp`BEtD&9^Q*SYGr-A zUX0#9f=tAC)OoT%#D2@DhB?X9`8${YE^w1()D$JId9p@ z>2Z4*YrydR9Q(7w;N|miu`>4SpWoqT?e{$r@5{l;7zTq5&)1-5Gb~VL0;`(GCWBqk zsWwOKZgTg!0Tz+{z28p^SEgz_?;H;bUoHWYFR&A&noea8$6Hmm+x;?Edrd#MK)1`T>p$+il+$@N zTpwSd8g5~#&xar*+XBoLV)v_f3HLbjb@n`(QY7L|Il2cTI5+Y?zVf99mdyPmAxqzP z{Wl_ikGTs(Kt`<)a(ct3r-SgQ{nFXYk&)=zzrQ$m8SIe7pfKxg{d@F+YLDbaq6}tm z^9p?F5lFhfWR^6icR}>P)T$kFtc?wDTN;D1X~lMv@Jg&6&C8QWB0Mm9(-&abS!}~2{;KrdFVJL@^*r9jIpla6SHBy{i%dS(C;ub|PQ{nF# z?7=_4r`aoOl$-cjW)nOQE6y=x5y8Bn!73{si)PAX+0}G70aJ#P*93KEy#i~`d6or> z61b&-DnK19RNo;?gcH+D)c;s_?T3uP0RnGcTso4AQXlb!ENj~LHa z#o}dsb_U|J4kUGdVNLdE;CMKEJUD`$GuY98WEFhw0vF$oT{L@Qagi8oR6VZ8Zb zlTC9}=E6|p&u;?f^|AQo-K8zVa3-HQVDJGp&k3&ZmG}?d5C?h#VbZ|uwuf`#&h$I5 zMuP)s8$!3H11oHTZBa!+bmHuS0(lCICGlnU6Ix+~ux8-RYu3~Z9=atpy#dnPnPrjC zN56m%hn}~4Vv(OI1-(H0X#MK6uvI18a)^5$TfSs#H4F`29~_N?YVO;NC!L8nhDb#m zRQh4VDs-DQ`(40<0BqWK;@Fp@-1P^&zr`O(?_F-26-0i?%7x05NX#ho|1detX9JRo zXF5T$N8~XFS($MGB}=3H(|xBEo4c2uJFL*swl5!yUN;!uJ(K?AD$GBtFz=*0Pe9c> zabl^-XxKY6+k2($8ql-P)q7W~lK0t7IbC9!u#Sa_MpI=!MogRRseDZ36N0JDQMvR%`s)J{DK z`Sy7MyJVK>n}BAbYZ0b|B;w2^uO2O%WrveIcpKR=p+6NI*X=`0@pNmMKq$&Q`+V}R z{Ewj#CrT%mZ?Om-c+y-oJlBmMO0Lq+=5Hp)an^I=siuEBD9FB$LJM_b*^4*5fFETe zI_#$cjMHD2hG^xf84!`jrGGKIv^P9wY5kbuecN%3l`;u~uAJ z&M_qw;*W7#we`6{_h{&HP1IZN{oVN_hz^`WVeT$y%0}sI4m%}YXOAx_iefRpC2i0w zok1IeA$1O3i0S|}&hAtzP!*j3)x8r}@`{!79n|hW0frBLCt+6K#vy~S`Hlk*+a zvlfQsg|$#%%<4?0*=E^(5qO8l8s<;o(-{p`^ey{)}vF21RO>kYPKv>H#92Au5V&CgNf()+iy; zaZ!0jdSu4x)=}L`|Et^AkB_&lEuEot0}1w28G%A=9^j_4}p33UW(qBoDn^v@5FG!WU% zr=7wl8TfRxyW4nvb+iLb&qJa%eI&%aP~3O({JdDP3RV2;mDq|WwCco&*ku}-Jpd$4k%0NXYbM@p(tDTT{WO%n=c}@+qKUe z!5tU{VIufyx;Qv33;5Y5Vvy1zjgVDERBaiNQvyFv;1wdgkeQ)xXZOP%W0%mpTf`-O zkyI;Jo?M5g#2N#!!RMq}okVAd8%7BYuKCR*A~y7LbDomHE?5*f!JIIh0gL2ZjJGIl zi{O8PCiTIFa2F`wqKO|Vga3ji=V@TYcfuWdu|_S>pfxjc4Sp`Z7cC7-N6d)axa6u7 z7zs6HXQ#GBeFEVlE{Suob|bUNHlKt3=)tP_>fBCO<3n8!Pi4ygf+oRL=4Ty_3R==* zSa^_Rw@nr#4Rz|)iPgk0-t0H!{|TB@-U-@NQJL?pp6~T{tqlJi6k%NGomd%;ulW6; zdUScd_xE6Bc;JF<8OEZH7A(M+lH(TPJ7{8q;<&wXYkfibDV3VnpB?v;Y*!NOT(px6 zs%4!V*$YOZrLzT>kIbwAy`5GlkxiKBG!_Uy>!^T`SvqW&fM}CUi=`5}g0zjnNl>G0 zB8Gf~YNuc-3-;ml;2X}79Y~CmJ^csgOcb_r+Wv!cR{p^``S|~VbD(y5|9- zIsZ4D^ZI|mIoXH*;2cC>Ncr!V(tqI`!>WI94#@5|oYO-84d++^=`G~o`3-t}kRtqm zVjir%q8qEcc=_$r{-lELVu-SGuv&i+#ry4=_+iD3@Xs}|?4SuK^GFK;@3W$wvd@yW zi#aAhwsZpY`bc&Z(w*~_p=M-P(Xqw%IOPENo+2kzQ!Cr8k;aAuXqkIO7|+$yTzKVa zy(UXM`chIjr=(3-awnmPU*+RwNl#KCi>ulNB$``Trrh zjis+yb=Gy)ehHI%qJUHf)UJ|9X$tPEaHOEdJOhz+{LMv+sj{;QF8EY4lI-NXS0Dur z|JExH88IRyZs+90_3bDL5T~}}$WMZcUjD%9wU+G@CY3<~Oj8hh;_7uk4J+t;Rmw2i zU4^}Bj#oh>)^%~lfc7pRfA>uG|Mg68Sr252zI!GM<%j=zCaEpdNA3UZnJoT)dM1Wp zULgPJnT%QBefLZ{J-&M;$ne@DBiYkQ>$|2bUF76-_zB`jG)$J_0%Yq14N*z}t zJHSzE=fVKTBgPYWxKO({DZ zQI|rY5xUxJRN`JS3*n*Qj+iDH!C&l>vr@J%;*x>@J(jT55mpqX1pF;pG1wZrp-`f( zBC&4JvC@tu2XK|g=$DgH#@1HERKycJ8KunWg+SB;#{|!JGKscEqjlf53?X(^N^S`C zuB--b?i>S&I`VwbWolNeny|RM)YEM5)}Vo#%^p?g{kH5tr5q16@mym-tG+ z4Wlt^_mSXE*%Oyk{u<}x15>Ec_qe|FN4?v>R9pA-KVSZwe-6+JeD0{u-+fb1+{w$2%D(sQ#EqCc~)c1^0@z zVYC+o83Z_r$aaY+y07R-l3JosZ2?$Xg5Cu|l{p-Pr{T7{(TpQ6#fRucSYy^9nsGz_ z_zptzZbwL*@HWp8ITATiu5G8xQv>_@$4n?v^M!wsxpq=#iqm}0Q*JLwii743J6 zcR?8=fOh(4jD_cUQ;N2AbiGk%?yN!0mTEtj+faYOXIvh+*LwASWrMnI)CVm_9P8l; zn>&I%`2RHX!v~fo6MSm-b)Bw8wyeB0xE~+-xs$Y)&w}gOrRT zjmJNRvWtgkdq2HWRV9%5^ETBrMZnOtPp>0QVRs}568P7l8BDn2dSu!AN->OyxGzEq z2x~zW3UVLe^Wc37{s2=~D3Ao`B*C`C%J?bUTBVN5s7D^uO-_3V1g|Z4>zX4}3|d@b zD6C!NtGjCA+evZ9cH#32_|G`>#S<7eBZ>@&oEHv^nV31WQ@HIJgEm;b9!jCWlck6Z z$u$swZc}rnP{XAb^7|-(NSLBAf~@a~Z?pa({CLV%woPr%euF0>p>H48G?7ip46q*| zfAJ}Gm*=(zLf}pIc>$K)MJ0+C3Ae?u=5uXK>JR=0G%bbsdENHW`K&|m`{F-(sVG=9)2(dq0W*+JXMS)Oj#xX|3B zAWj0^#4mi5;pGeWl~#55Lcd%(D~viPIUGQBc zZ(gS;;+xm0J(Zta|6g9`Eu_ssUux`nSoXn#MQefJfCa~-VSJ33y)h%qs=>%g2V zg6a`|4kMqKa94-v?#bSrUW$N82~Kt(;AWA}fb%OFomb0h+neZF!Y&912fDDvK{8G& zCMu4zVTh&>7Q0uMJsgR!d01tl?_+-gwAB{z%WsX-{uG`-2y|$R`7(h_b7+J4(wfC4 zCXaKdjp&nC&Qbqh!VJ&|B<)Uy4~kfEqdS_HEDR9M+s}-Y^=xv^^Ag zOvXv`3>v1JM}kyi1ZMRv44yAm4HOSUZV6$E0v*Xek(2Z;L^^?6db4Q6*iz1dKg67iMDO(>4aZGC>9c3!NgN) z8X5~P_+3Q?9=&DC*`uy$FRlj9Hg85F=jkq1EkULyydqidH5*W#1dlIT zi1(S=+~pnx5%e1PtLPTT^28KzBc6S-?xQojxKxQum006f0wI&`cXNdwj*>DzteG+v zr_~z1{umN;L{Jov2X89^Ju)*y&&uJF$80)Z?~gY`bzHU}freV=*pLh6jA>0PSV7Gr z`MJ+KO%XvthOb#=#ug!Jj*7O@#?~q{{CPoyXAsRE2Z&mVg8l(VII>V#wR#E@o5@tN z@8V+>UAT=tzuA>RY8w!cSl7OxE8J3qbUTY1%{E=gPzaygv=QNu(t41a*X5VO8j;5%!n)&trDz9|`eA z@INf<(x%C!<_2(G@W_n{^(mqi1(vY;gBnRJ06`iL`S3qhS*1#W%f%~#Syf@nCU2_r z1tEy^5q~I9H|YxwE>E^P>tq?bBK={dkW67rK`@@-Jj>=!zbX=O>jsL(6H(tX$&?P%3oVExP#^BRPWe* zPSw~sf(L!HCP+lvg@7zm^fKJ0d=&cs>i!{*)LU!{rZ<;&L+sjP-`)6>*_nW;l0EL} z^9_=w2ky2ml?@c~rk-m_NpNx7NWkC8W+g-B{@Gz8fhUSHGY8X|A!SXGsg9{{jHQli zc(=@acRgS%qGKE_F>}N2$seJ%r&b`ZC0FqBGg2w_5a(p=Cck1JmxsNBV;v}|-4?5! z(Qhw`)!bb`M7a=mm~kMLhkeh?U6k!<=L^Kg0W481J8Vf#!-!Yn>^G0qoMg)(#a|z>TQPgzo4Omt&{KnSjLVDv|XGp15MBCZ7=%HT+ucdZOD$ zIgtyK@$u9A#%unL5(UjVXEk*F37`G>4K<r+;Bf8p z>CqLW&Dr|o(@&WoxIHxHSAgTGQtd1EaOHj23!#SXkv@*yX~fS1K@<2x6mI@3bPr&4 zf*S(s#T2ugej0Y(NecV$t=dD+LOtep18M=^*#H2&b$Wv2a@e7q*Q0h;m8yY&pwc@G=S zzMiv6y9WzM`k&nF0%|P|tAr&#cS0X4(4?j2FJBjwEb)3opoX8Zw2j+GCT{nvO4XDa z9p@v_Gkc>Emgas3fyeq}zPd+Z_Sbp3Yi`PI0L}&kq*CeG{o@;X3d z>aE{TXE0s<1`tSHmm?=X$-Pk#9}Y)pDsQiI@#37{uZL|$Cp8JBmZSq^Ia#4;{=9QN z4W0Ag*35G&6)2aEs|cl1__?jC@>Ljqw6c+CQMJfzE|&Ze#=Is4TFOh@1bx15mz}f* zB}S(QH+)%SjbKi@ zthdcsm3>*e4Y>=4$};p)k_DLKWR<$v=CFDmvBX)mS@1?~Bl&-q;9B%=3CZ6}(Ehgs zI}xj5;Q7mHD-dq;jb!8XWbmk+<&9*zST`+K;~$ukW53HgXtqQpM0p7SMr_g-^uNJS zZJNY>D-=Cw;x~sE>pm3Y<}B^jUiz)`yywKA+c6UT&#{DD{EP-So{OZ~(AOkb#pUj7 zh>AKXC!uE`+xyVNLg#_uqkGG-HLX`PE4*@emr|R_u=Dv2p{4Tdkt{1vP!QWgL$=t8 zhlXascYDNsTlrax4g(yL>sbCp!x)9s-mRo$jg2l9e5K`Xy3Dp3n_f7iwblcHtRi|m)h_cbncf$2!9ID(S+wVxn7YY za1^R0)uM%0`Q*4CA+$yWRz54UCZ)i5q+Nd#pb(@F3M9fPb9LmKk{9Y{Ema!T`6q;H zOVe)-W5r+p%SfXe;vlx;fW_cyhL>v-PdViFBQY2njI}d{iCz-c-#q=_-uokCTRi*U z-rHF*+6+s$HuYH+Xbl|oq{HJ{*krqq7)~<(O4LKM*)KM)p-HQi8N&w-?M$B2N8;x) zex%=!-IvBQ%wcJ)Au_v|XwuL!okT>wF#vzstHJ~<@26EyF{oBi10A&OhIQW_2hWPY z2P~&0zy?A%N;Rg`R#s3eUm+Xc| z|JEdYt?LfaAnDCFoR-``ow-z+$b##}-hN&7p1BnHKQ%2G=@nmLi?hDpBxX`7VMmlQ?~c@*BDTvZhXwluO$3PUIRoz3>{pY#Sxw5IPa2T zC8{ttC&&j%R~fMaDw3lna=sVmniTbIiqjO%?j`YMg82^=KOlC8%ST5!rwL7>v)t1G|N{d-jsj8hbD>q9ybGS89EIj%RQBAgq77McYC$xL}?EGkE4USwsiKiJ5jG>c6L}O|$?p)eH6>ScmCdyRtQ-AuvDD z$3~60-J6e6B4?&VrV%9x&mlCHnEt42(m-^OOq9k`Bp`<8{R~K5>Sb6}9Lb5qtJCiO zLx&DIAn%hTfix)FHJ0>;3J8&@@(n8QJ46Pc8hH}sIm94~8~uZLcO@|-m#~FQ(QYth zsdmR)Azx`_#h7FTFP$k05|bf-2i4mhUc9p-g!>C8{-VL?xd32D!=HQickj>3m&~sF zk?DjTBrsld9IXXmCzVv4jas?XsJamv6aqWcH~WZ<6n)8pme5Q7~@AfPG5bZhkZ}ShKdp0NQ5E; zoQUXW-KkeNOeT)&cHOmmROKQ7hnj)mweyHBjM(tcV;hZb8nSl7xx zA3n;1C0e#bH6$-A`KhMKr3K$NlU=Hea)_UZ%Uwf&3Dkl+LcxeepCb4eZSK}UQVcz< z@1n%q4xu?_cH_lQ0>T}y5yXRHuaZ|8!<=itnG&v_n1(YvW{2uS5UTXUy-P#tyF=#1 znKl`BGdbEze3_?@g+m)%%$0K>r`!gAI}2t#SwH^5D=I`ed_#e!{n(yFJUr*dc0*x? zdUG+ssuSB2d#S4vYJR;gw(LX@4H%dT-PQy?jE$3uRXD63%Q>o@ij)*a7=yN#^8KRLt)iivYswfg`VugDDxO9gigDgn~K2Ii)^`QPQycyu*T=)l?hj)ZZ zQOBq`M!ac1cxsL`IdXVFHXj_L!}pKToY7<*pn5ldqh6su`P5Y*wY=;F$Hj*G^zEc1 z{|{~V_+96^!2Lc>!^XC)#?0Rzbd`?g%h=1Kq`zzQnfnqT^fXAdohWvcuCm4&8?K`VIdf3Qc1Xe&W;JPS+52N75F*2c1pD zQW#Law_KuzFOdQu00^aM)98tIPD~fJFLe1+CdDt ziYqFdqWovbTsg`8mb7=n708^3*8WJE@*INXMeJb&y1$=Kq|AZZ973dLtW}GNJ|@l- z;IhVg>j4vIac6_#|C%_H{xflQ22l$2&7EDB!N7e(%+@-oWGK&^y+h_=l0Yu=+w`RE zf6o4KpUSNb`~0$6uE#3f*F&E`HomNj;(`KVFBW0FfP0}+G#QFSi4aeL;PyFAwkD8? zh?##tGAh&^03>V1{VCD9x2oc%L#h5!PXIEyC^O@B2-m5H@OAAMyp z__YDO7y!|#(gb@KFx!O6tmHJ~o@mEa)i9ktX67`gT%Kh^83Du#X9WK5V&B*P)xV2< zCLhH7Zp!orkx#C?J<=mvVei4O!0f1yC!({K7NJ8+eq1T~`N+zc=0cBb8X*WlP zR{%&ZC`$+YYMs)901TWd!J;92fMmdc{2!3K3;@ZP%m9$QDB4Fyc~%Di$#zZvkZkEC zBLM-m{1-?L()s|&Rk80WTBbDzk?&?pSrTOckUXOT0Lf(k0m-Qqe?am);UAD}cJLpN zTz_=}zX%He$(#QI$&5PykUUmm;DE;~nt<2AoO%ZU$u#bdDdp~u%C)BekUY=hxhp)^ zT`5@bSFbPZqt|z9L-4H6HcxgE5m-P2VG_C~X8WYq(1t5Z@<`fru^TRT+aa&zGQyV8yS* zId?c3MrRfMl*Sai2(0R;Ln^6*T5fa zC9&fcp>WPFuQv_mL2Rl|##_`u5^fY6Dgo!)7qz+vTFvd&p+m=rDy;Ij3TTNd2zD zL|e=RW-7DZ76dS6D8K#run|C%@wr5uQQC zo&V0%0?)U-mfc(hRDgi1o2Sue(X-g_bB5oP^SR_lN!B)(r~>Gr91jlOS1iD1$dB!r z89$XsD+m+Ns1Q_?pF_(;QXV61^bpAq(R;>v_Kz#6GcHK*WNOVpvbK_(63^_+mCcfw zFBMc`i4__KvVA5#F$Gl|Tppy~rpgC{vM{k9gib1i_}z{2;*Vk+Ob#WIgE%B;Lt#X$ zAU-Y$JPqb71aaN1oDZIQwMfFn9u-mqrEoCh8(P7OpS~GG?ax0>0cnifoasx6I_u-(kfM*xn%gttoB;GCK+;8u7i5 zbS)R` z1XuKlUSF*NGi_#fPgxxBmvSMmpVd6&Lx%WD!m;~@P8b8}b=F&$?wgeY6Kr5ZsrJo+GMCYiD0;K4U5dkfak#{*oBEr;V!4JlQ6@1Blg**Nq1lpWb zO0j&ZF4=`~1kNp@aapRcS3g@@85Mjlt9r!xTpo*EkyAShN@y0^?0rHOwqgPRdYl~> z;tNSg_oW)0IRymtd1&3Ot0z@_6Qh*o?xAdg? z<4X$aZksp~NCQtW2-En)#1yc`k><0!jT<{TuKUL17>hBqt=w)(&}gjYgRJ$}&=%bM zmb&V1Tb-?LdQ@K}vMY}z!^;*1Rr10njzR}Z;Ll5!UnzoTI7d!2+w!f;Otc@t(@Sa9 z4b^pSw)7j;A8$+cNdPkdZaVg1NV$a$}qiAzle z0T(^VVRkBHKJf|6i_&FNt)5&qd<|*|z{oQLbLVd=A2FDWfh+0P)4jYsSXx{ko_k(K zT+~SAMwa8Rj=r(ur2q~fqJg&DLoG)SZ0PCNwWH`Zez{e$B1kWE2{C;oEV;XIf{s?o zZvvDX<=Wq-y?f;{-_AZ9xsIw`jccbJ@z4=8;v^;OkgQYV*G)xYOLdSrI=nhBZlSCV z@85UH$rIi$XHOBy9o!yQ2OY1NZ`}ubBf42U?=bH7+l|SGgzncPwaF9j*V_{vt`p9{ zzAEt$QWG!WQ22ubn0?e<&3#OQg2%uYw&w;~T+iHhvTu&tQ9Dr6G&*i&xBI)5m%Bqs z7u-7BGm~MQk=icWmLCm#Yl?*WgVf^k9<(;{YpxSzxqn z=x<;C6>xl)hxvR3y@3hMp)iCSjQ9&BZM7IOe3?Cywh%GBgj9=fY~|s<5IM;2maV@G z`47S&HOR0^(EoQseue1y!xOCg>bXnEnQ`^fB}$Y7ZXQpOCSb)lbiIHv(&bm=rv0;D zaxdFas*%bMVJJoO3bhg-8dgqb({>;bHHHUH)2y};Iou1M30`e)EWk`)Q`_^)%Y@oG zz9~Lm=C9V4w#VIH?}r&3J`c~=$Jf8PZcn0I?$34C-?iI4crFFOsAgpzaWU)uo(YUU z;<@7u%3%QdiIcYxTMKKCpW`-WqlPf%34>lLzzJ)ENN1|xZ;n&5pQlpY zoJPL8&q6P|$cA7nxA<9pD$B^+#M73T@LEuiAjQ2--1pR6l|}qw(IuBYzD2w$pRqW4 zNSomueR|Au!0I-6Ut)0t^NA2uecm?#O%#j|U*~j(5be zneFef^Qmmq`Hq3}M5EXy^u5|pn_96=pg;R+6V|N@#3S_Deb=srBdibXx^4sWF6QGm z-Ejz|u#r}ztcdmig#67tM0ogf(d>0|K_1+zA-L>lS!33Cn3V6T!ho7%^L7}N@I2LC zU|WfTW*Q3%B=9cMy|Ou(+J;sdh}KYPPpc&A5j$&11fmHyW{jhi%+Ax9Ja$=78?_x7s6p%JkUEL>FBr#PKmk(ix$uiG&*^DL1 zSkf3MxhX>P5L)5oe`#$Ss&Fx+jN+#`*rPF?a2pW)OqH_gy2|3m(RBStivuNIHT#!1+lR)9!!!SYe3z9 z@B?Co>lLpPH~n8}#`FsbK$`Km$LUpDGpxOKzV^ZFn|?Hy@_qU?Vk*z`a{8*De zB%v0#+k`h@TIN#kVyB!Lvte!pHFI{UsV%Ws;1zMaz#iwiLpgbl#J*RQ)h{Fy@X z7w`C8$#!qHeuYSfGU2ne!>Yz(84V;}B(zo)tb)rzy#M&m+Ti-84A2k(zWo7~7XjCN zFPH3L(#5|Rxb4!bvi9|Bo!1w~!F8>D6sCgqWF*UQA;koaDMy)Zf~H+pmR0f1bb*AM zfSz;dbBKdB6WEpfpQ%Oz5gZ+Gdvnywy%|02xHfwth->zx0jlXyYoyi#vhiz52jHjB zympagb-wP9sRf@$_a(wjiX!BH1gJN&k8YQZrsdN|@fs>Vhj!gTe5?&F)>t6G47fzH zMF#C~4Y28Lk-9Q}tPQ4);mK~jP%hr?BAsS9oFVlD1G5@0I_dZj=lx{W5?eMG14>Aw zc8Ha;#TQn(Ox(Zdi+UE!>9g7zk$~?#G{!DWPolKzsxx%kK$TWgi_vP)EfE%J9y7Oh>Y5BaXlt2(QTKq&Of{KgeU7Tf0F1#KCEFKcWnkgKWR0q=v2H0a4_6 zB!eqC-gTkS`JwFnSsDyhON1`pMAU*4vJEY?l{~bD&89%--a=rnR+tQw>I?)d4Z3{Y z=BoGcd4GFoM9-`JoSZk2MJM{+3ziqFE=_Y~Ut#R-LsWUxsRy;cJP8zLTzaPA0stt$IGmBMOL==qyO-oH-1 z)9F2Sq>`6`!{Q~f@fR+C+HzR0k%XJ&)r3$$BI7w@_0f&?r~CijXQWb2z`eKryU<7s zge2w(RzELsZzI(>XVky_b*&W8Xk7VMqp_LV6iCBgbxT!?v#^0W@(P0cY6Z(^{!xG< z`>i3Ty;0Jzw{Ogwrlrq|&p&=|RUK3=VC09*N%$JOc(*x|6_Bo5g!;^XH5#b_jmCT$ z^hiLXk;oEa^OO=T9nfg(SO2Xiz`d((9|&=TZ4^6P0frv&FrXc}WBP=)G&Y-H>nHld z^(wtVn|t(muNkm7SaMDw2v{6Ex1i-!YVz30Z3{)k5)m)-BWW*BW5Byp$L6}IXcqp; zW|;%Ml*f&}Wp{wL@1t_@q;R*;!%af=jTv@-?Fw&c4qVNFQD zt%x>I-L#FgU|E9Gv={gxa!|`ZgXl&kclQy4mv{6b_pYk?)1eTbhpK^tmgN-0HoD7^#aoY^=e+eupTgfiRuxL5vFe*-bfka zzrF{pr3gDWH&|jD%Au?5ye>!Yj1UYmv#!qDmf{ua3oxV&J3JPB#xcAX>wQ~gaLiJq zHHYDnrV7$EEt8De2E6L1D*By!Ge-*i4ZFC;E;}6777^A1EnHhJA36)DI2r-9{nuHq zZ!u-WupJm?^>Dw?r!UO5&~K6WvU;j_y`r)uOdO7npd62WhlKjn@*B%LJb`ULCgv@;yBmzTZHaaB{=@fS=jqc$Yu&L76a)}UmC)J0qXqd z2RaTNDhXhEFlR?gdAB_VSPKzFT6i053&x8mcL2C9!j^{AdE%G^^dY)fw{O*g0dpm7 zhaG5@l;B^fMtr676psH&HD+S{AF0MK-h-x8f2A7Rw*aX|kB?Mi^<@Zg&d)IW82RoW6kM^e#*?HkTZI8xSi}g+Zdp9WCXP5jjoqUs zc72&>Bg$xc;af8*G-SPdlv8<$~mVP z`M37t72jOLjeAo+!RZwP<_U?mgVsBo@3)X@f;`@JNMk9T;qCiiOe1c)@(_r-1K;== zwt#;OaCEjaZqQxA(D-e(VBbF8?}Rvj9nakXeLg086pb&0&Oni%f#qAvU}~?LN17%i zg%RyZq0|QGV&s|P)zv{lS3aP`JnDuN zuJj{hW2>7_n9=-|HvMz!H0ZQZauMdel?3)^fOP%hcm_Ma>-YRF7AoiD6O7ez-X)Og z+4^tL)P+nvA=a=2AQxLD3ADgX1fR$&Ju^{pa*TD>iQK;UBZiA~aKBKoeKMXhH0u%E z=V&l5j29pwG6HrLT(1zqQj(&_Q;yfEE(J{u`g-^R%8g|IrFy%)PK*8hyEoKRn_9PE z6hn!A)9tK-?Bgj?u7xrd()I?}6f=~yAbU46D2Sx06z2d6RPWKPpCTQ^mmT0Z{AZ3Q zq#fXyP40W^SVt+LUIy{)1F(&>z{QZpXhgAfLi%0$v1O0U+=9XOLFQd6Mi|$iiiWV=hm4WP@2emTrtH)9-)Xmd)*P?Q# z@yUabn4j}Q`mT@;l8XLcYK*&&}3@OK0tU zO&ol&W^rT{IGE3j-Z_H8a2XOjO|d%Lk;bC6U0#1L^1C<51iv%90BK~F-JbiMu)_Ac z)Fc>p*4Ij21}zuo*lhK1>c|51so5gxcVExO;x*Zwh^qr(i3o?sVv*;5$v~Fo_st?C z8n)>)I!J4F#`r=$B1;f!spO=-?mr~*2>cy-NlFVGju0JL`YO3UKoiqs4>!~vMbM&U zjq-p}izAf&QSi_=nHa(LoU=ca+z&s25}U~?ob$0WkNx42hP~TquuCn{MU@c%!E;qm zN-X2>1i~zS0D>56Ipa6Qp37rj>INcP8M!@>W&JR>@-Vc=woWS!HRyfVXyBY*?|K(e z8m{=|o-D=gzdn2xKi$f)^|Z)1pn=9d3T@fURW!Qq!6HFQ_!k6+3dw{qIMh1RGS?=7 z-oUUD@BjH6;iZC4$pzANrw$Dik5vi{l01%BU|DoyC3t#IXEC2$HYW4YYy>laFGU?4 z?S{lU`Y(3!)d0QXj{*1Ng!nMvd!YaW&Ty>Yyz);2{=-V()6jmsTo_r5HC-fbC{nltZuh8#b+b(_-g~U{!23 zs2@ENz3!e7X0sLsp`l?Ex=15wOo#G@5BYuF3#AIXB|vCNh3eor!*ep459aJA#zf14IpncH_7J)AGkI zkZBv!F7?uTx$jPjZLCfi3v=c(*kBfjLG7q0m*(Ii)D)~|ZKxP7Nu2x(x(A@y*h9C z7{9>qCQxWd2ZSI}_POKUP=6%VbjZCay<_i-NJMi%@dK;LySl4cUSv!-? z2`;>6C|(>CZ}5o}D7&SCM4!w}n1Oye{swcc?oVwtDXPmThM_1YKw!lZDaH*}U)+1K zXa8v?)6(*Ms6QM>xd+wRxl}=_O?I9zCLt}|Oj&d?)6}v)R6EOL)kY=nadmgc0;jyD z8gf{aG}DHssYLSAOSMe??7M7DLRTa{~E(GOqQ@mipxwbEA>iKrX z^+!X}#K!n{&7(V8|F_SdO7U&df>QG6`L=+g{9HNeb}$G=|J9b34voL0%= z7SZSfWb|8Gv?ZdgVRd@dtm67qRweUxst5)^p%}Zd9EQrNu%_@?tErgp4h@0o^+auU z&T;U2e4Wniv&qs#7m;3#CO2fodB-Wbat=$+hU!?^3^QE3M?inEEBG3@tHf{ptfAF$ zxlxpt z*@+n!`-)~TgB@*^*Cv4`Bm%q^808pN3QmER4;%QooILfw@88i>{2PF@+JHNR5wcpotY?X+6hJe#j9?h3lRu8eqJ{~dfyG`$+G zxk@wGg9Jq?$i`Dg8mEU%waEy(-3~=lZQFqFdj5MfQ6MnSbC>cOLK#W-fS zTYnBERi7PqOhsi~>99^Il?r$ui>w5-!l|94;&)crAz9$a>e`x9Zr=A+4L>Ms3iXPl zVx+X&L+LA9!vt4hY>O)E8Fo@^KC_*~uk(8PaC0oeN|w`kH*rZpDcgxgympp$l5k98 z;eqzZ+i`&UX|{^n2Eb5n{V~+~;JR1$_x}>q4|JH`N825B8I@ye1LedP<}WMd&)&^< zlXu$oVE{wj85_;pFeK^@KP)5TQRg&mK`L?|N}PSquX@H`YiYErNR94-zTrqiWJi3y zC@QdIH5q`MRgnz(3%)2vGi4Sm*|H};+8{zv>vUj_mf%p3-0mp*&b2v2#~fQ?zC~pq zd7#tFAorTJx3HOzbxuNAj-9JDtUqu=7Arb1D{WOQ4_gddeMYied%pnKS`8=a`jE(a zqCiun-ymvu(w@jeIaqm5Jda6>A?FF5*pk3I!f~}w{`Xd}=B(FQSX5OEQ(JDql{>S4 zfD-6^8af8l+u}A!YQ8y4O)ZoHB2or))l!!73tZb(udtICKCE63dn-l7?_=a0EZhwU zubmX5vttQjwvEpLzghintc5SnGHbPcHlUK7*C4&IA(4}{Hz}zW&VliR*fMmFy@o1P zTqnZdeI6zYOvqLS+SUUhty`T|SIc)w6U*~*GOVyq6xJj}5HIZ$XbqiU&w}l!x8X0a z62y|?L&ylqt|(5*(I#bc>o$s;gKFs}$YyG8Rsn_kSbtQO? z1Mqj|mvIi)~bpgs2fDJ?7itWeW7oMcehGDevR(M<hvG z4^eJNxtynY$qB-+w1~d&f8tI*=b0R2euVe3G@3#O$vz}rs5Yp7xb`9rwuDz7Pj=!K zsW%&~QELJM)uePgHn4SAH)hxpOd15bCg7S}R>;s}krDas7)H|wrmy0-Gq#h+*cUdZ z1XOYU(FrZZ{pf^NKLI+S+TKh_QRkT}NNxu6Lgrs_d_w2~n`#symtr#K^^A{fS$ahq z>s2LEUKH(T+r0IFR;`P(JLQ^U;g)O)^@pv^`g_Q}2v+kLG28HFZ~5AZkMZOAu`9{X z^I`H=Uk(dbN;E_n8XX303=pdD>Qti5m_@Sdz~_B2YIE?f^tCc`?SmM}Er}-QP*$=0 z70c|KT1)6a7IXD^*v?WyEcO>2Y2V+KP@(^=gyO9MDxvxdtYqzQs&9bbi4?|kZKrPV zcWuSA)VJdcqLDU6;}I!MA~#d=iM>~$3jiPVgLl#$OlqU=XwQ-nu4@5Jmd^ei+#I3K z*m}VR%MNDb;@&Pt^3{-;@El(FEy|UP=v{PFf2d8*AnLYwS9tIgho*`~lv}XJA$g*Z>6mUUnZm?Bo!sjCqQ9IC zJVbIlfFPK`^h)4AJ06*{1oBk74YA=8y9mcc_WB`4W_QGm>S!$q6{1S=BDgR{S4$k? z5pDmE=;ILj5Pd%gY5#~mhzRib^I8T8{}qO%Sf0JSN)3UM;dAL4VHDm!qz?$G=Q-*h zq>p%3T2UpAq2+`0scs{X0Z3ozI}tj;7?o>cPt-c8xR*&{VOh7 zASz!Xf{R5_DISxg-5#z376hD+nrMkjnX7P43^#9si-MRHv6S5fs}6#QO4p^XIG?p7 z3mV$79~lN{J$NoXt9rpCB^iyP2t?Y(ff{0NPnL@%+ELD(4#adM){W4Rs-Od*yoDF$ z3aE_}cOoq8qBSZ1nA1+r$4GOt>Exfz1=K~uzi3;=AZR;Ns)Ioa9jz*T&7iqvJ?-6+ z$eO-YXaUX?rh=ZcH)7fZWk5+iLPd%~=S|k2}Hu~ov`$6Ltu>A~6M+hC zZ4;Np*PIzY98{5tCTWTXfLXY?eyVF@j|khUf=E6y3I2CSX>G zG^cDOS_NeLO8V-_u}pA*MF{Ez^8rKl@4l#Skmd}qlqyqi><{kffXgV`wy*p5nDZQQ z!yHkFKRO9yTYE)su8?sfhMY?=T>9+Je`4nuD9|1k%FoVthul^|EIwbN0yU7ui`0_1 z`&ktmA$<{YJ}EH&Z6@9}!U>_UdT7iHNrJm>!F$IWT>FT3kE>Bp!9sxmI5bB=$4N}e zlIw~D?KSG>fxz0Dhin``W(NX+ljB~r?EU68ObmSiE?_H{Ol{Oa_nQop51J~WIblr8 zcv%&+I2}I!W?gR)uzD$}%26Alm)mvf7n7ZgL4)^b21)ti5ZIqt{5#Z9|3ZpM&yfd; z(I*^NaarIyaN70}r#eyCb80+HciP{5QH2lAw;TPQvT9V54B&hmSrQQd&ZqRj`H23* z`M&k;(R;*;CvSV&i4tx`8xm2`BpwSUy;ELF0Ux^2j{!`;C!wC&gTl?e7hf< zkK2=kyb#bAh4%Z?7oGm-i!N4D-0Kg_ksU;U70`f~gwB2RML|}#dj06Pga7nJEd*7= zM4avMFuE86f&>A5QLi^hESaK@xaeqCMk(HkGSpvj(OY_2Z>cbypdZJWl%A}KlUPST z5BfPvS{x8f0CCX)Jeg?BU!P`-rDe*rA4h8lrOeZUT-+3IPZiK&cqfq6DupiSnnG%a zL)&FHdqG{&puYLB%{U$K(dhR-6*sFrOx$oOQt0LHyuTtO%sGKDu6#;o4+0rs>)I}s zK+}yMC&b9Rwl&1x+V38u%|n6j+;K`P!BO0vLF6_2{_l4P<*h|b&6GBr^B+1 z{d0i#i`y`#$i(78R#B7OE!g{SH|Vt--OGamN}{r*EcpzF2(QqP~B&vf&nvbMr1j8(= z^^v}LucL1mo?HQzFI$%rgUE3z=<=fb8Wh?RggjJ<_4W~o7PKvK49WN?F#;vfDT2?? zRnRM5JYV!;*JoeP%5a#}l0yYwlwQwIMv*r)H@K(v^-tc^wDxUnaQjm}j4!C0Jsq)N zG##6sjCH546P{O3MM7^O#6-mbyIIO2&^xnt#a-x-V2DC=xVFM)HpwZu_c~CzDY)O{ z7gwFAYjt)5k-FIrV@gA_)0Es~`hh_Mqn(={iRwy!aw$zo!%UP|g}2B$HIzcTP{J)k zx%CcFjH&by(?&ugO3Z1xIW+}tm*V!(RZD}E!KB)`ifTnJ?u>(U9Mzh6BS*9YzkagW zq041d?^-p)dTIPt`jQBvc*0G|2G220}1zeQiF3h!`T5GrhV)mY8 z;$Tatqm=sTUWZX&RZ8#*AEUNKYXz{VK!8!(^(y!axG!6u@=;BWh!TZabwXT#T%Nra zKl?+)TM@i|=YkH^|EsZ?1o_>BAJEwJI0!`QTM97S4pYl*q(^*WmIhj^+8iEtWvmZN z!ecY7@weroBiY>yrT9o}%EdbCyCeiD1x(DTvSpcf4V!2m&d*s{Wh^A0DHm+iOQl^@ zT&}-Y9t(y?EcA-xVZt8zhSDcXEvIwoWh80ntZ$<_X)>R*^aKH*9BkP)coe;nD3>%A z1<vAogm1`#tBM#p16LWFu{fW`?PoTNdmqO2c{2S z4yKbV+UKIq#)KJE1B9Z*aqhW-^{xU0gYL8MVI8iM4_B~W-Y<6xx;nfsXN?E+I&asf zeOd3yE2`U7^}K)h~`GYHqjdS*{rL&m)KTbay%cfi?Pq8}aqx zY~_YrZfd1*iaou@4*92}zS{5-5r$Ad=e0?vqwq!0`{;>)AH3Jx(L3L4w@_O*rr#C~ z`H&*kl=K6M4cTFK?5hoyKq9~Q6@*ozq&mJ_jjaa0r(tLt0N zxy;G85{R%w3Y>buZ{evC^=6rSsARB| zNaiz`IFC5=VUD)KC3_B4*&ptyV*HPLdhj*M-=%ILan3^uW^tGBwhxw&p`D*u1W|~o zQ%C(e#KNTaB%4M%i|z0B^vTJ(*F#;X3cLCb~L z;{CBf8<>viGiVp?K2%OV95yCMzaAf*`Ds9A6QgraCg~`#sKZ*<^q9V0PJu;GI5(n*mXE#|HWA9jwJKGi#&~N2q zD>H03Ml3|`WA-(HR7>V?5nS1kdCCWy=K%HcM&^cs!Z4TqYIAI>Cgcc zQfGmktft->xpS!`WK~QAfAEcx5gcKS?)Q{HWuC;rcgW0`tWu@4tOlE4_@$L$PwK)~ zglRWsJ8=sX<|qd=h^`Fs5<=6E8^15(-(g>{o5_DT@G58}z~{u_67TcRqpN*E568Cg zqMjb6CTeL=23%J{ZPi?gPTgfrA5WM^eI3S7Fb8H~%LV|al_5KmqWsR$p;D6B+Q*k` zvLmUyjqi!P2DZNhNJlS`8az|G+w7u$2Q%8FJNSU^Yx{HkvvL_Fh17MCuA7X(3R^|l zQ}Kl*F|c}>9o;+ut4nIOc(jYL%L_rK3o*F^y|F1G5Vi4v7XKLdMr@(oy-V%NtVDeF z;R_BL+{};0!IutELb~frK?VsX?VdtdnWmwqjuL56EIqLXMu2eoMXh|c@)ey-J_n}4 zdt}WjLV<#S*^Rj#%H-ON!VRj7NsyAKE`Kj=kQ_+ z!>%W7sJ{^jH~o49jL%M0bOOd_=_QgeiZ{=efTc5vBoKty)MmK1j@=f)ws>e)E%!#0 z%Jn}9BJtd%wDYLSir!bavLz#VZ6dkJ!&`%zJ6muImZ*c_bE4(Wv!x;C%yk)JC{m-buM!oxZJPPg+D>=^nN~gm_j1j^KrB+-(GZB zU7dh7=L?r#LH#w7=Ngs65-gv{T1xiPBsOP>elf&;EabVY6Y*4tWX!1RYa8)apZ%`g zdFjI9>I@T7|jE|qTTsc)ccgf02NR1F;{ z_1QPKK^KW>S#$jaG$it@&0bTqkwuqk8l_&fvJKwx>iQqNiDebC76!Z#O?M2)eM{eW z&_U^a49NQD*C5Ty9gMGZeC^n_O*W>!8Z(ePcF!xpoaXZ6?9lZxrg%os2)z7P<`VHEa~aDeDFet{w)ORi zojcpeB_@{&=6xDsj_uVrftpZ=@!CWGUd*14*&rWu+KmxV8OW*hH2Br-AgP|3i_C9_ zdx~z67}K=7^IKg%P4=2DG2F%o#dTGWzP*z@;ZsXM1#bx>Jhm`IYWl99P*R0s9}0Xt zg><>i-vhJ}K)4{TcafK6!$AF%1Sda|jhVl~kRZ0Z)RuKWX= z;E^(F{sWud4L)Gg-E#E62W;wJ`3G!T9DrE`xe@E#UuEFULZSh{CJ8DL4O19NxUCP^ z6!Zr+k(y)2)Yx(V4V&z=<@lkqG)->AQQGUR0zHa<*h(^jVipYt^L?86!T|L)i#P73 zawjb+o6kh*_=wE*$V_OAPy1eV`S}AjS$p8{Zi{n0`ckXX!!2~WjF1ao!(c?v=TeY$ z{FIC?*>+$HL}k~KgLDjT=WA&@p7+{w+6&-W%fjJ?3$XZ%X9eY@$#F;o8B}JW$+0-x zr3!y6$57ZSB6JZ8Kuz`=0MrEfftvKua8UlBCjD(K$=&E^V69l>57d+cKuxIw`ZWO5 zRKWf>YC`@uYVvi*c3+P^V!gd~liCE0#ZW0r4Uiz))mbl1w}KE5pe(0VXZx*LiKfQE zKakoY#7omv4P$+5tO5sFYFxoNOg`HbNSA*mW45kiw^`&{Qly^D?I&DltD>s5rb};0 z7y?_m=CZ46xhb5%1fn)ymj5y{=RD?;qt*)fYZ~`ZS9^|pFgmg_$xWSso6wNW8n z(QA*G63oiqj=+L&hC)rm-O~XFuC1FAGx&BL>B*WPAXR6ioPUXJ-z$%3bDLL-H{T^T z0{Mu>XpF$)Dm|anTY?FX7IQ+kLZ1A~&y7d{LHdK!Y5yX1-k)KeKqk;K{nSKX5628m z@#zSQ>K-ms;^V1H1Dest(T)PdfJMQee*5uv_b`4o^Mx~L*WN@`f9~|6KfF2{_d)%58PnJl2lOREHbzI!9?KHqXf?%>mk2}qFr z6R$)9#4FP=yMX=?udLH2xxkrVMjq-!MDz!O9XgLDpGqo(+18i?9{V}!@*6nw)6#c! zbKS30>Axg9JU!>ByyHYudKskMkD)2v`VUtWvvd3Dw_7n87wmC&MU7o8G_-M?w*COFk z-ixRbn$9jU^|7zTk?WB*6UfnH|9FaN=4BrhZ>a{;yuPtq?Vy-bPuXIq_vNE z0Nu)hOZH$uw^IC{-O8X-K({hv=%ZWdfsPd2tVDth-6U!bIUDfLZY8QSQEcnh+ZOGi z2<;wQ$~D`(8GFWVCraG!0|W@x&eJn(iB}%dt_GP(p|Gh8kT)Fwy99WP<$YYx~k5CE3819dP%Hl0x3Qex=o6Re&7~Cv857MDlHbYA{P2`Q{dZ0;=bpNH5eh2F?${xP%yJ=|Oep)~>7wIo{ zhxyP--oF8roS+kNIF;09sfkeLhRK_I0w)i27Lzs=GScsl;?5TgF|*J*CKaHxw@z|1 za|T%VivjHqd`M$eBlW+H+d$sr);zqiVygCgEbf&rZz<%?;M=U#uv|MPJ|}zi(9Gc- zGQM6Z{W67FxDk7*3tmFGsR@nvS-5Lo_US|(aUCV7G&~vN77{Qq>)Ju7i0jzm89|N} zN?kGro^&_#wc%4zJ}S*>5h{%)#Vm9ZC!rIGe~?!XivJ;yS(RF6g_l+e42_s4LV)#V zoQzd3-G8uBv>uQjbv(ZA-Y5#k0Y0IJeiXV;qjq$M@;WxTJzSmP3JN#(Zjc+l> zM$KSBt?H{DaM;gUbqbY`A}TgYorLMJZ>x1%$NGxGv^F9F)X-lQCd2&))PB`#VfKKt z>r9A<-j-O?ZGhRQeX(5COK@}xE62g8M}(kRa-TM7CobXa@}@ zaGy5|%=5kUp22&eQ>?*9oIh`dst9DVCLs^#=^5aZTmeoAK}<7l>$9>TMLunhK9V&1 zhoj~);eU)CB)ah;U zCLsk*pD8lF%7vLqw3iY`>7~z!zJ!SSVvMAHU(9oMKGX(g2U&_RL!|~}0ad9$P;ibt z*);)8>3)VwcTJQK^@>JO{Aw-_%o37I0WnAM=kw@bArK&%QzD8xhWqkE6gd~O;36B0H)-B2`hmny>Blzf zUA$Z;H9%BMojAJ;3$;nt5$;1Jpc<19 zhTNri-RnWJmI{yGpe#TC@&I2oeo4ci8pj!v4bIeTyh4tO$|@eUsFyZ8Q9yyOJIiMb zKhz-2&ebEa(i183Ii+dXo=i;K28tM%SNX~!qGancn;1^ijpdMoOnF}gQ{8VTs>80> z<(*$vULoDAktxaCZ&=&a12?!R8g2pe3*S&nRO(rDy0Y}{hXD&-np_=CH_<~3jYS?u z|99hSA z+zoQ#1#u0lzT}qX65-ikNX-=V&fhhP_Se;27gG+k-j&;EqgZwKEc@b^8d&^Xq(WZ0 zabvWLIMvwlw=$YQJpBnJwZ>je2Wz^)I_B7&)~ze0=W$3zw@G)Fv_wbb0cK=D?16>7 zr1WFX;zz$Mu?H;RiFkz{Hk%v8n2=Z*$(8xk#qpqiPuw2N{GSK1lm~bqgLu`V*h_{v zApMKx5=lXRFH1k8-i|F)`Y^HvOhQAjcY3xK<@*|C^1NucHy87~0KwwCrmTT|T-udM zF(8SytwAE+RKq|PJ`nMIy(zM?x&l!A(+!(^o;S^rnIWxyV7jU4RlT5vBjD@h@foyGq{+dX*qxh{IYj&0j^(%80b z+qP{sPGdE;+t_wv+eyfjN=bUS;z4yD{GtL<2uXu1@_xB@ZuAu>e&9o+!j?C;5 z5);B2VhL)b&7~GoeP-GYl0X|z;ALCTvV2=bB2qsje~gvL=Pq33lv9s7$T{<@Lt6p6 zMxxh3+~nUDgzUB@ZbE!w0vBfaxf23@duHI(-vt=;GohBLNJx{lkTE-PvQV)SFzYWD za$!)$B4)+)dWSuonMsNONOGjc=u4Nb&NI2axMmx&xOF}OsG2pX@J{IZR-NG~O)XrP z!@DFVpg0zHvS>gf&}>WrK0mB&!Y@=Yt1=!Kb)Ud0;nNqA{E^?#WMfR;UxMx+ny=a@ zY8l58J4rVB=(R+|uyTgqQR=Y3-P@;uyQ%3c zBZN6t9Ae^mPEC~_)3rr7Wq#m8pW8}Gv>z*`;rbE&7q&h-$6NHZG6*~d!3gSuf{iv* zRMMnE*qr2hp2!588flMUnICo$47jIxKx^*`@0!UpM#xPV;2T8-G6i`}`m@0iw}HsX zl@&Wb47zto&v`Bpjo=bh1VuU$X}73d6W{=(38ro-$5+_HJI9VIBGSg~Uw8HQA_h>7 zg$Z~O(Q2#XKjX0KxxO+QEZm?i783jGBR@c<3EvJ3TK07H8}^BrBwpS{63-0(ktX=A zQRlT_Myh|LNq^KLKcvPL>vvGoNQCWuUi&nxHA|$NG=A*yQpah#Al_N5w9>&Fw`1YR zRP4cwo8uF>rBnnB5lhzq^VHCGNYdf)Gf0S@>LEVps?hOaBA;T`^>5I_o+C(SRDY7R z=KT4+fs+!|JOnpSp-Ek<4s2R?l=vTPfGumNC` zBTiK}>o1lIpjnNWE}QX60BrIsDG>um()Rs@O@#j|HbHpDCIzz(Q4YW#1%!)Yl@D0~ z8&gQrF{|v5?q3*;*D+iq1_q!r5yOp1;~P72#qUPH2Q868Ju%2LTEtjE+1EQdC;5;` zhUDY9bn4Ah8L0ZF&~~Gz2hbEB?i)ZcUp>S27R?@DgK|C};}vn?>V6f+nVXx4B_(~x zSAw+amKcrC0s4Ydsuh(LIYMU1C*sm=wx-_-Ht5cET5PC%q)d zjEu^WxVOgWd7xg>@IhR$6eCK4vK!-6SI^7*%%TcLL)|lUH_6x%;t91W&9M96|CXd> zC&K0aoupN~Kr0&htw!1`lu@byYDD^v8Yvc4WEAs5DX)<%`}p!HIt!4bEks9?$HxY! z5yF?nT1rf$+?dxYWAH!&(GVFXoR7^G>;pi-Vq7Ipe|DhIw}J34&Qm3GxRznd@Z z6A-uCs_X27_aCh*XnvvqpplR7Xk_3WjquZq%{jBOY>R-R1)rcR#Ug3-z<*OzDj}p7eSKjqvvM6(eKdSYkF^g<>4(QmL^t_Vk(ja>{Hs5*HUPhFgd0{y1E;rnv+p zT+9~SI~t%q3$|k4J7GYY`>Q&T(Q-yRY+O-5GHzV(gBk~)1a>Q^0`25nEJV2M)YD^{ z^6SpLAOTPS7CI4KD^hTw{sbRv!CW{su$?$7XaeSfNIcS0@><%RHqs2%VgD?#j`eJw z4LKcjCu>`TOh(X$g)UI%z}Pim9={z-*qwoUh(`q!bz`eW$`;azwK%++P{sOV2nT9l z*`h~9yhLTA`q;|?CY-HA!YWp=xNI=ZWlMsD2l`xk3aLb6?kZq;q^=MuS7Zst-gG5_ zL-1lMThko_y~qHoifOtE0AN z=pyx1acMHaCKB;Mn5qckuH3Z-0Si!Fk%vS%{W{}+EkIq8$h0;>48A96`-1!HZbQ&A ztO7kJIEMvE7oN7y07+WR_XQ}OXisVdFWwzJ!brmRB&}MyzX=@yc--i~sDCw!MdhN3 z{LSp64bwJY0cx@$r&?rY#_$Y~q!ouVM|fX=;^X|Y096QBfYPD|EI<))3_eg@Bm)+p zl3W1`P*i?$R^X$He~}S_cQP^tNYdh&G#CKLNPDi78-R@bdQZ|4{)>z-{2?O@f5=Gr z_P@x8+T|Gn76gEdRQydw>K6fI#IDph8$d=n0ZH1`O8^;}aDT{<^8h4iw*X{>0zgJu zO8{hK^-q$v@STiI)RNp8c`Q=w#}pS&yjes}zmt)>)g99yMv^~dgd0Fcz*(L7F?-QN z$lu9`_j{6d>8~VhY|p1t{IvzhKWgNdk={pUR4**$w4Dl&q@Bi@t+E*8PU>)Zd{5F2 z5XeP3ZGyfhX;oI^h^ATZP!$15+9mP|acN&`Bv8xU-3;% z2pbqdS+0p+19}oXNBjtxrh%pSiM`Y z;fhQGi`sq@k$7nyTit8-_hlw1ry&mjOx5>|_ed?Bzow85U0&DBpRo-qWiA2tN7)x5 zr3+sGunh597h)kp2zyEuWuU9XiYBarP2q7@pi_aF{%#ftd*AdVFR(yU3Ys>rx5J*UZ;2PUWK zSvQLO#=yK(8yb)VfzcvyIxP%Fk}{OfNIIU?$QAM9UO34!(a6sVsDjLR0z}DP7H6gT zeguu2tCl(8;qf&?f)~t2bzuHySS{^a*r{}p@u{~7%)9{Z{eU*&U~b7*DYarIdA$T2 zU#AJejI8Zw66*jSQMBee>Epgw#+2&bGf8)|k*ei3!%!xKry?4H<3rW*Jf`Mpx0^6| z_qI!t{0cfQl`fjKbo7tA%F?y4Un5f#wJW2!wZEb5#ut{{c2qa!R^ay`Fw1~^hP8HT z6I(a@Mmu4g^v8TS@joDMTKrR1C3R+SBEOudA1taMaKZJ|TXW*`Y@RPRUw;l?_t>yf z!^)mT9$DOgwOBYlUC>Nw2e^{VJ~;>AbQdes`74aq<}1o0}lVIc++G_Q0Q7+sw-?26Mq~ zvT7VbqZhrJiLS;AHoKaA6K3(;VsFW(PdW2PUG42>*k(@ivyPVyOjO(1&VsrR2nC=%awIfco&wmsA3%4~K5R9**_f{xG`y(3XPb(8rX)e(JiReb+F{YqT|ovW^9s zGSd&ZnX%(g5wedlkj)7e$9HSB-LJQ|D-PX!0XMhl7L2dY=>q)*4_(jq%b4AKBGdZM zKT!BY75u$^+z!?<=DzLnt$_onOX1M*U17E@oG|1_+(;YP2bp8h5!$EUSl7omT^!#n z@I1~(al}(fQhOa#&t<1{_oYxOe7W+V1LHK~re6oA z5a^H^|8!X-`i|{SU`<>B6F0Ne*AzNxAKCI&J zAYjn-P&}Lf?!z}pU{^LUOQ)uG7QB$k3YfO#Bdu0Vbr5*w{_&XhdL8#r#EmI0OA{8T zc$eY*aqKsYRz;NgoBPx7)K7{H`1CaffyD5jp1;+{*QMzFiV?u&DOx=?vk4)clqAjg z(*b`3IENc=s6^l86CCy zxL8gPA0H6@`h`e~QjfF>AVBatcS=X$7+C*1cS;kqiFf)}?vx%bC-xJ(R3y~YCFx;2 zWC${@KO)i1XWovgsu?H);E1bJYjVkG{o$?V)MFkSKA(gZjKj#PhNA`HpIUah(OYoJ zngtF)1PHCumk~9qFo?ds0l6SMaan-a>H62MiZ-u{WV`h+?l|vX$TMKAjJ);cM>}8W zGE)>z{D=e&@x6ir;!}bq{9-@cOOu4z3f_KliSigT+h*bXT#VpXv)glqGc3x!ZV$qF zb(iQ3eeA}1(skRzc&l>nemDPPa)ww(Kl5nt~e zrpBMvsRHAUuVl0fOaG9>rOBbL3f`1dS=k66f5?Szg_cLvJ}fIzZ8l_1hVjyUO-oi|V5R~830dP3a2Ban!+e<2X|@T`#q zC+UZ9X15#?f6!9>V4HtOAS?39=~p=k{~(agBnX@40_M3X_TbD4zf2SB;9o>p!3oR( zjF)kpP#^5~bonbF%s~#R-d~BaFOPM4o_N?5orL50#;ICGb^i3xt~;e7-1olF$&R-H5wime4m30^BX?H?!F9 z3Pg+P9|Z!;7Y>xco<{krZTR;8g#vN-HwE$wq?zZpCF z;?q?IC=f4z0vS76c4;_IB`c;>!Et;t9q9cJ1(Hg_C>X5X9!LaGAO|0SS{3Vk#o$rl zC^Z7-1WBB8INudWq4a@uFy+v@0x75hD3JEY|4<(0%1f&n4^-F`$vH^|KAme{r?vQ(z^SuK=z&A6^NcUnY&ziM$dmLkjp=% zQ(XLiN~g`DOj!tN_*J;YRBdvcSWLpPvTeVlP_v|N369BHoZErql9l^X^1N_P?>F}- zyda_|i3!0vqV{-G#N5_4f%1du-ae>`_lU_mD?sMz)Asdd4xW{P`OX0GJDrN46F7q# zp@{%6U&Fa5VvWa!RQ6aOrm;f|1zSEe56xjP>aF}6g0YS@m;v#6=#SVmoeQRQp`3vt z5hVxfB}D0O2tD5XJO(kT&r*04Nb%7x+d64lYc9FRXgAe#=R;%-;9-MTRCD*ZSGWD43`K1h;HNv{>;kEP{ zxPI*@*0M9vXOH(v0%iy{nl9`(gZAE0d0N1{=xgnmL_%RBR_~k_k|4K zi#d*#Vy;XxsRo2jz0eW9&C`R391-g2ADn%??YIRlx>P{;8XnXPncosRRb5p@LmQ@J zrlYkoHm0tAs-RIiup#0w2Vfw|AjYx~{mcLcB7}?Q`-g!X={oPrrvU4s{NA=Hfb%2i z7~W}n-?s7li-Cv?{k?4y=A%ekFOWrce>@Nng%p26z=%}Tsu`&D~3}o<~fixFaP5~GQ;1#;Fp=-|Vz(7xp zM$$a4b|uKBI4bL7CC2L-l@!+oz=6Qog&-4Klg&%2%6R+E|kRVvMnPA!C38(T*t7)US#H z0zUlhK&+4bY~LLSc^klifMhxz5t(#3wn!54!myHP9wUNb5Ttq4f!XrkU4UHm%t0~q z3ne%R_;X6k=<6X~=i3K6kdar-NF{ymyPn_R+8d{3*ddy0Hx!gT3chN$^*sH8kaFxE z?-DUVobbe=Pe=M7h1!E+J~5Xha`9}9_bg+)%HAZl_ldHAv&`!O-yT@h^yAQ0-(wvu z9}&EVm$30xT2n7?{9@?ydOJj@i4=}*QL;z-W$y;iM|upIxz18SlL}VfV|h@LXM%9x z6_dd6P)FsdBduO!?b`$TWD!!VV$JX4jkV4piWq$1>@cGO!vd3`EHk2^ia;(SYi^Qz z9w&>T(whwGZh|Q$;~N2iWy0#z>iy|y2o^VUjFfp_Wb~VmDTLsPa=ja1^3sGC;<3O@ z`@5kFtx(oO9WAXPLQ|{KTuZ4@vgS`fODy4>hj2**zPmI~KtPmEdYx|&L1%@F%NuYo}GP5KgVVO;G{x<{wB{TK@a zVVESL)g#V`_G7jXc{8lpV%$Nu(A^6!VjAK@x65|(TKc;7EPho8m0co`fayw{mxLZj z7k8^FhaeQnH>|22fdcET3Xwz}N&p7g5i4AW(&=8G!ubAb0lpD02$f>7(Xd5ER>20uYRiP$zzDl?>Gln|`Od z6nSNFAd*^VA%z4I3A9~R^Zwc6MgXzLv(orsdsU14;fKS*OzX7~J|kt|4Xtx8uH3MQ z_qMnLku|V48XI-AwQuIsM}<0&>K?cxHUA3=p3%wXek^_=V31JLZ0R9DM~SnKxO`&d zPe%#K2qN@PN9jtuvXki91NwCOaWjt^IzAd6j~|j3%mEH?Hd~1yhUSl& z>bfpqg3!Dh;aE(yXzu(!1_E(g7=;yh?BtAfIM^j+4}hT|PJu?+F@Jy(RfOo6?ctt7 z$dhR$il#WCZLR}pBk+0f&RzgVy1CA>D_45OungI?UTfSZH3T5*CY01selCbB*kgea zRo5XH{6Xvm;2_=z*|M7N<2CNan^ow~6Zb$(OUrz68ZlPiOc0+o-y~T_FN=Pr-@xo6 zxS2dz@%CwBRj#NAEfqa?@?3Z5S#7`Y3+VoUZ@k7Ff&h=K271hau& zLTV#jxmTNVPL|0*Zb(}4tasgkeHa(7hZ)FNX~=9Zqw@t?hTkQ0w4k1%+BI?P0n>i{ zDz%f=-TM3`7dn0a3UApZZc4<~Ki?d9d~J3p75Mg69T#SNiPa1Vs!Q$eT*`( z%^)I1Vm@C?go+)ywRYkz8!l#-nGnx6I^FmT!fL27foiB#p&fOwW|Hz;Xbo28revUg zxHg+y|0DA5qFg0uZ@$>vG}4zWaI&{xCf*AQVPk?qnv0#5+S0 z2m3leHM|WW*yGLtUQ1AanEu@l=zk2HJI{;c&NYg#cNMh~Yj6c5ne$QMTK)6X^Za3X{f^pa8c7-#Y4yCWDKmIZvl%P-T-vp3W`@@*4$B z`ANoIkxO)u1&X-5W9V=;uIUwe^eRP;MOyjt;*{+-sa9@zmVVwwjb|f{71f;~W3EBl z!oeSKjrb=pu7JpymjY`1`HUSw#6Yc+o?nH?7zlJvojZ?hB()M+k8t*IKtdylh%9jt7z+?^n7 zzCsUoaF12!mo<8=`b`B&u?dp>5+{C&U{l0td2>96b{W~{3yHDY$%R4ZO(62%GHBih z#}LmJr#K;C0nR1O+%&cY%vBQ;qVR`Yifh1J3)(Tf1lGwsUE`b_D+lqeM2Q8kEqG?& zPKrizMjqcFD+Y=pcVoOCG+_m;PRrMOynzk^>1yFwpmi)c+#yv-sVVRAZ6&C>3Tf~4 z9i?AnZtkUw>@4aPsV;I~kc79MG?p^kvtJ#?;#!an;;K zee%bWIn?ct!reZuWphskGn6Me|C}ki9O+w~ob+2+XB|&xuc(?;GQ)+2B(+%eQq2qt z;yhWSSzSU_GsjZHRa>%FwNuqfS*Y19Z=kR=sNP+q(N`-RgX^2bQ8P!0{87%*td6U8 zC}}Or>cpmLj&-8c7%auELc|gK!3IQwf1UFK^i{Yw?>g&kRiH&vk_G)QOHn#)R8P5a z^Yd9N9DV1|S*uAFg`bq%awU$yw%HE%y%Fv#u*nW0B*)ulmQF8y0iXhJLH_qM(OcyG zjC#-MEqBfBKEK=s>Mk5%^#y?Jja*5o+SSEO98I+Cigk$9xX+~AuGW^NCe+Xm_TnSf zD7jicd}>a*ajZ7=%PDU!LaoTYon>!{?`Z|QA;+>&$LVSl)cY>7@K z575O27dCO7%Zgu%%=bA~V5FGS_TYNF#TrwfbKQavW8CM{%3o@1zOX*BFgi7z%j z564``hFAy}ZoaKn$6X&yhS1lWF#nLXClsUEE^@DlTUtI z2SlWGyhr9-H9$Hk^`uSdli-Sb-c#iB!)-Lj&8?gUP0;j@X={!wB7 z=9iSwRkw|0k8ifVlc5-e=$wZsVV2wvO1H#u9&bYMht`<>$NG~YUm+Cuy}?V3a? z$uGFCWGbcAMt;lz#Z{=4YMWJb>^BTeVugOHdQm7j|GOr6{_OyHMF`(=h!_v+fZyf9}7pk02WLY1P*O{Shr0b5oBXw-w z&rms@J9vSpjH?gXMXOpU7+q1f)2X7{E{;Cp+{l^Cg!I*y1&oc68qL4NCN~*bq7vAD zl5aKv}c!j-~zTBR1TpN9WpWj{TvNB^uI!R?J-(J(^; z)jk{5UKoj6FOTb}BC7JMZc$%vagA*KupZk6*KUu%&ZOXp{adLS5D!$dm#w|i_t?G( z_;{9?Qm&<~+<}!yuybp7cH735BqMeRXWJrMn3)>QU-MSuBh)ILrZ8pBfGYxr%r&4rw(9D6+o=sMdHhX8WnrV)8% zSSDP6n|BEWjk}ijHS_I?EZ4(-3C<^RbQTJ_K)f#pgfb?>Xyz7La4W@tub^(4g7rK1 zcN+^#SjjhgFF)spN0iVyYsf6)kX|Jm?6Dpi?BSSeNzRrDo{@Fr;fFb|O>ECkn4fCN zxx^UwT%t1Vkj;hfV&R=IcmpfbK1vD|On=R0ap(SkP(kHD~R zC7u(yd|#v?0689!ec<=ARRk>3R2>(g^bek1AN&%JUB%1$#CYi$Z9R)*H*JOu0S;~k z6nV4mHQXBt>p;>!B62HOCgB#m$clcyOy01M5^6LX$C02iL`SKf2lP->RzOHpRb~^ypFxSNS6;TgL$g&9Q0~y~&@6+6lHB`bSQ*|=a&#Zk; z@0T8+w)-C8pf8R|SPcfU{hvSG#6>9DTaXu>5kXIi&qA_N)e(Pg9x*|&U>?h&38BNV z%d};e`s&mbT3oi5NJCW%c(Y>_w`Gc0z-~C@s@I&5WL-B2xt~=XzQY6bi}gdZfTvdS zxZZ}30kA{Aq-a_{UHQ-xrhC-6R{(?e9aDWuuatJFF z4122?N8Etep~sov!%^)6ho$zeTN@xTSEKt{l1!ZuKuu_bvITva&M+O&qaf9bKDoks zN4~6B10ji*+6O7e|Gr97_Is5EjzAfEz6@5QBRfYF37t$6jnj$y*pf`8iI&S8CTst* zV|w8{XGY|P$P8xFEWT&CYz|%D&9!wq3^TkrfDOrG^%!F_h@Y9&X%Zpxz^0J3!Kc8l z2=PZ`HHX4~i-}5ziG35O12)?P&SRk(73THCl z;>N5bBh?nn(fG+AhdEj~KY#CifjS{b;lG=(w?du2K`ow=GRP{4Tt8i`xd~uH-oM!p zjZkL(M)WD2qEV7GZKpN*Bp3{+D(!J6cY|orx`B6fK_cOzTq{*^4Kn4*l}v zx!*?TzJH>Q)mEF>qM262Gng;0 z5lO1q%eSRM=6Rlg@T^S)4P7T;es&CvSBzit1%k&2Hj}oSIBo-O);V9$$NADsM)wQ& zShNjVx=Y9m_EXL|S%vTYSuG=vc=}8tpd4pa-uGUPD*%+^z*+(2xSfj;x&Km*^YT+N zpFaZEyHpJ}0As=kD3sFfoJc zdSeg2pIK2Dr8Z4ckfa!Ea*n!0)T&rP69`os&LUT^edke+^Hn@8Ig!W!(|tPQ{;&zV zfXOv;)~Hn6cmOU3{r0P8+rw}Aqh)G-9K)qQwZROUf$hM0PW;v ztazmCD!>h~AZ^bJ`v1!fS^uXSTK(;YU^DLA|K)}_Z2yZJYC{;>3Uz<#W5no>K7+r_ zBQqYFGgq*c$Y{mo+J!DKo`v6h0m%@*E8rw5=`+UO5HVu?N-Ush6%CONQ&&67OK`)Z zq$8hC5>}zP*G9w-t#nEfe@Jc0z;p};XvbBcG{;lQDCFpT@2o*r|Hjt7HS>2nj%7Uy z*tB7aIJ{>c89OvC)Pz%P8pcQu?Q`KgmJ)pIUXzS12cf{MQ;scS6tfOq=TrytDRyd+ zB}|yWg}3CJwO~zx7;vIZpE4_K5#~;~G)3(>PZmYomJ*YgK`G4F0r#a)h8-f zCY&)>MwlrjC)y##%+Q#U1--N8k6Ne`qJqBWIi~r@-hS6Hyg~E$GuHzl)iPZn80u3DBve*)G25h63=k<$ z{HsD^xr7Dvl-2?yLEnBQ)8XkU2=nF{mw$9OGwUgZ>O@GBocLPm8e)1$cr3))ooY-# zXS0tZIUFF1z_yY%6wstQQ*7RtkU~!zD|?9EkMR6mhP7ZWP0nGtwFolE%Oi=gWOx^T z;r1`iit8A%QaV`k$fI{Uw7Isk zAIwM+^?PmR2|$MiEuFvO^oE6$hydtN_&Xh1|C=;VKf$9 zR3B`qD%gy*r_~BLv@W&8s&i*z;%go** zFT%CX#37re0{-s4-$LN$2rew&`)-d2fWBMiIiT;hZNatw^=^f`J+un+FqF`z<_%P0WPIt$=CeX@t0jVb;>&#AH#E1e z^pSMEa%QV2hJ14?|K#Abo2yC)zFr&bkeJzo^28j8&w)+hGpg>w)r3$R+oAY5e}ua< zNs6}8V8M5gPnto00uS)doztIjc(MaM4Z(=G-U@{Gmwe+^%mUK-kO@7RPQkEoi>{o8 z<4lr>evr}P?;qiT*@+x;d2g%PiMaa)PqRLVn zsRI*ul|9)KWQ(C3C7;QM@o#-3U)xMz1YmBz(CAwp7D+%VBh>{$=dwulcr@-cA9dC= z?cdz8XGpvau4@6Iy$L6OdEUJnM1r{%a1q_kLwF>-lq`r(2~b8Cifvpjf;PaDAyecZ zGmKu{ioX%V7bLGT!YCK2c3JA4=t!Z}haxQys4xz(S0``A=U_$RUS)=e1gdO!kj?y1 z&F_G8$IVx^p{6z|bFx}PMK&d2RZqii)vFpQ?@gY*oHuUSz6zLY-yys1FvN>+9?**P zJEA+y=Kmey;tC_&`LrU&O0XgDvwfKL>j{d+u6%^f^VE|_1Zb@dk05lddoFoG_EF-m z8Xg6>y$>C7?H!#VN&DkVBxw75Oh-;mKVmshCgh`p9<2r=TimmtR0D77`me^_KFXTn zmBARrTmjm>-8gyJ`Z921gvvI0wT6!K$ zVKF$)gj<{J$IrS3Y%ytGYVH!SBsxhZlkG8B60$RTL7t-Qiw zFcw;T`;Oy=<2wL0;@SUWtT)Dx0igzoL5LdYMha%nzHRz~epU)d~YpUGRcU z?oZftD|gJPBF_@WR>3|pP5$lA7ypsqQda!m5?rJIk>D!hs`C%|f^884#4P>8eETKE z9el*@Lw!cvcVVF0 z67|LnCx6Z{?r}FrPgdejfy;}UYZip^;D0G_)f4+}2!l1;KLIlc1o5UXTYj83ydZ{W zYtsuq*2C%dTpow2aR}5&cbH>?Zp#sco{RneCBUUM{8xZ0evS7}fXm$m7SAHE&XTqE z@dBnMxI2tzhgfn-Bx`|A`r4njctx;Uwn*zEN^1c(^};0T0;%PNQ{UGhGT%P`z?wI* z7{LQ$z||&OWir7bu1cI@k8>U+9w|LvGj_}Tf6`lIhO(NtwHh+KL;s%MI!d8z3aH2@ z#s_!KYRD=yHl)w85E|5O&%j#LPI;U@4{HM>o&t|r>(R!;3nguHFpVanNE02ihJfVn zl_vQ#sA&C&M`sE82Kcvmb9zzcVqCK)n2yLy9Y*!jt2;JH>wfO^2L1L}aGIo3-WocC>{{_El9hSgQW8R<$8Q)lY8 z?m@RiV1>Pevtof_iAwZhsr-~^)6N1piDx=u$`%f5tN10`5t{0KipBt9$UpF*m&NJ# z6iw`(DHJAqxk@vtL*n$n)$EUFIUD(pDtx)*tPpQ3R{(9-O1 z2qNXL1OArHg~x7_V1VQcUJ7!b#f_Y6TTSV8?5;4+B8vwmCFOjVe%xJbgilF!xZlC?a_dz10L$iTo?2< z@2bcpXfdMcTV94o)TPGTasKjb@!c5c^qhhlV$h#48pR1;jZ#s(IcxzZ$0{Q~Qck!{1~9z`i>Mp175!Q{ zEgORl5+=@2N=J);-j;vtLQ`PY;Orb^8t*=5yNbER;kCLlp|)?WSIYO2Sx9+lLgu#3 zd=zs}XdS0tRA|?1u<)ob536DDp~BEL31wTERpyNmDi8iJQ>f11JyQpQQAYhh*(~SW zAAHdb>)0cuqWJ``8&7->Im@^CBTKj6Cbwdo;0{E|FfWM+r@>Gam$SVYbyhMJ>#MHZ zIO#aRE&M5lL9kNcaJy-t^`$_mO+U&{cAiiLsAT528RM@cU%|!dgUg8c0p_Wr9M<`k zwYT(EBmLcdM#6*yPz$YhxRZD{uw*_!q)S)rd2y64QG{) zubNP>-AFtULCT$@U}7i7-)l$HT3!_aJx5^;Y-g`=g?^%xOBCmDnXeLYfa8#a)@QN5 zyuJ9cgoI5zUzA1PN3cSTDy-3UE*BN+6ctVV$0Dhhs^-8YTv*FSiCD&}%)y(m%C<{l zMNnnLkr?V08oXO1@z`3?hyQVrX86w{jXb{V_W|xGB!rAOo5O~KYrfbTRg}f zziX_71(Ndnuz(oB+?N#S^NW(b#==5R+_y{+o&Zbj6J=7G4S`k zexRfyc0U==wnRJh%WuQ`3umQw9YJz&8wzx1KaRgx2COM!>Xk4jGfho#;;cI&V(Jea z_&R|qh`2wh_0Yx8LywQ4VdjYAJI&sF^1$B&|EbjV{R^yo^(bs2k_|(73_HE3HGGeh z8r_~!f2j9}TR$sSEM9WL43S?*n<`i3HvH{t8?4hd+ZIXZcS97087fr9-1(N62(9kZ z0sutuGu^w2r5jmtka)5#jK(x#0_&P` zA#+SZIEJUSN^gZ38t*vDy_aCL91TLh2;BX1y;e*=hCJGG2R{| zjw!rFgDHuKVH*QZxt%2)2|e!|DV^<|BUSw7NS%GXb?+Rhc7{pvzjCC!|H_f*j)PI1 z7y1x(UH|4t7>4g0X=3=DBQ?BpB;%eRAUrm4abfQq$v)=g{_Z%8B8!XD;z!#k>lfC}(0Mya* zmIO~`na$KKImkz1q2APM9Cfyf^h`B`0t?;pPO-#IM9U;xTi=7q#l~mEI1oz|A_M<{`}C^hM10cg1>(* z_JpA2Ao)Rn@^Q}~8O~USf-#MDVHEC69B}h?oU60zfI5xdhQu}yStP|$xeez_$<28` zIoaFK*DumU*>`yRNq_chdL=@UUV?xIMA#6Kg5W&>`!ydloqO3d|Jtv){Aa&L6a%nd z;|kcX$u9N9C6H{3ab?58n_t}aVNl8SuI(r5__PT z>`Vu6OFs@~`FgSY2eqdAuN-5jNXA%SF{UE)PJmBp##6|D4JjucDfKak_J1N0!pZ~` zw<_GyZP?c7jaKFwJ}%k7sFxP^M_djPrOov#l5{dT?qJD_{NknM6ozW(5<{^E1I35| z+-gv>M;a>}*21W}jU^v8nq>X3Ef)F>=+|$!RCZEQChgA!fJ-X7*^sxuo3JaBntBG> z<~bhn-QRt`a;bO5=ua-&(5(LH`OqnQOe`%?^q z!XndKb=9>4Vlnz=UtaagvjqZ57j`aei_su3j}_`#XK=F`b(Taxb0t~@=`>)+=GBXU zj_^9*sAr8(=4yt9pqD#xF1&(pxXQ~=UN!wm2{BKdqZI14t-&iOM&yH^#bl(eq6;Pj zR4d8>!iAkRDK<&YBq0?K5jCBVK(Nl}2)xt((A}yuL%}jkBc>+GN|iX=dGBs1LaXnB zv|aCnGMQYG*-S-qh?))g%$7qhbnLHD_a}!KUo@}!G*H%A`8F_oh>KtEd-8l9ECK_g zG!fk>4A@#al(&lD0#vo~0>>{xfA|Eys6bOTP(>qv%a*u8)6BKrr!#j$i2>pmxO7?r*p<5dWed?ul|l4okK zW^!kDsjo+hANsL+{5XhzOt56fjN;>)Ul`4;=S&dg#~7Kr<9_o1;Q&FF>?&O-#R=nJ z57Zp7S^7}2CdATO9Anh@z4An?+vqHGzL>SQ`+s zJ|;TS7kDT}A(VmxPIJ?~k8ZCHy!y{Un}7~@j9E|DxAll#QZ9M?)Avyu_6fkKjpEPp zrn7$^wdt$Yh1z&eaPcdx#{(1-*F8~L)UxMoVcV>tvsF{S6T&E0+zxXCUY zC;pPRh8GWEwxpNs_5m09tTi+^u?a@FlIWc8tg;|VUC5HZv~vS{{D`LpknF|0Y3F_hX)Vr((#~ZkP*+Pj1lCukmU@B@8!X_gUtN+^09x!l&_k-0$h_rvONmdGr^4tUtFm{773;b_mH>qP} zO0v8@o`RoITvlGSrsfV~oik9Y8Mt~g85;pI2>~FJ1RlzXK43JBQn31VMvYRO?gR|# z?$o2mp`q5M*g5om$4Qa>2Dw(6Vc8rHL7DOGv$b-LZJ`p5bY;%DEZ66dY#kLPv}h0? z5*V3#cKE??%c-^xLde0&liYCnDILuPz2&AkmS~N<>%OR(4Nj8nRyEKX`hY%)y>?4O znQe)G>dZ&gLM7hg>>aAmTf9gRqPzrh50^%Cks>GsaEQ6B_qgm{A$49OhN3eI zGjae1$rh_UApVMk20vS*mdU!`CEl-SA{r`zw!+SG>mw6BCHJbx@})SP#jk8*GbNK@ zBOdiCOHvP3x-U^lJk9mul=}7e51-nq#pu$bXDQ2%rKCT0OP;x~U?2&KF^^$4+w0bsVcMgn`P8TOi7 z>Ru~?E!Z`dPoPHJYqJb;O2Tq`iu5it1!s6}U5YO719E5)6a(-pUco@$f^&{M@0%ty zUwE~#s440b^_Mb*E@U#4^*9iYb(TT(mv!LJR9OdZ;cDT_D6MO4A#4@7&rXPQGhbY41Eoa#c#CrO1E7 z$vte(e(zjct|YFJ7T02#wYn5~eVTI`RNd0r_+g2Y2Q$bhp|P=bW@rUqj$`9+SB1wml@l^7>TT>&uN^iFN<3<_{tEdfZ;y!mtJJt?+P_@RMA7ic97d>YU-D`(yMDMga zHy6c2+SU+vUfl$`U)JBn@juKE1G499iBK*N+}iKSw4UjJd;`8vA&=2(8O*m5z`2gS zst~?XNZji4GGdYr_ePs^jrlCr*oSHMmO7FPhjOt?e&c+3apef5IdLL_Lb3o;p8O=_ z$ZGL=M5pL#7A5Me`j5Fc$6a5AXe87yW@!kfm=nUWyAaBZ!W@U8YnH-!2>g(O(2)r= zOhSn0Kk*A6ZHM;xv=OSN&e&|E9F6a}dG3zztQzve_gfn?8Q6thiN+_mos^(1a-M{{ zSJmE}j`tC_68lzv-w5g+80`k`YUT8;u_}BDEOC{qn!qVI?1Q;Lw*G;!3e`M$Q~Aiz zyjT{9wt@hEARK`!sQ&XJp8VV3p3i~yRQ+;JQS_$E@z|8V3zi1E^8&Lbe2XZU)@Eoq z4Nn+s{%Rg<3y!By5$Tr}BLVcJ#*R|OUvhCZGB4j&^~Ot>(d2YH+$SrNn3*6)-*9`M z&P>tm62s{wi@c13r>Tc}xW+W|sP^-`)2R`m0_k@Re%RJC z1Sm8K5szL)dg;}(u;oi+hS8SI*)q`?4Z3CXfmmm!6Cq*jP-KM_Y>k1 zbOQfBwB1v9Wox6Z>)5t!+qRR6?NrQ)Rk3Z`wr#Uw+qPF~jDOF0&9(PD+(-Rt$NiwS z-oLv&FH@!(-q*yJ3)1xWdXg@D%=I@`=?ba_2311a@7+E`p6Md*b{8_pmbE^1I$oNx zK~MbLxJ^5VxhGXOP(fmu?*x!B2cZORG{&LtBR+g%Y9_oE;5n2&ax>j_(sk@g+NW*R zo-(mdswPGrHnpm$-v-24Ef9eM6D_KXR+Ge<#%iaMn^_c)9^oOEi+>E_I1*Sr23wGr&pZ7sy)P>b~?68k#z zha}VfgM_Jx*9iUyQHESmR9d1!AM2DxAQNHJ;g-R4$X)Bs-jmA@`vH)80bDJePFe-Y z0x>0(8euXlIBk8yGwVbk8y-(Q6Nuo;7RJatKTlr4iCYyUaDul<1^MesKB{*K9>#|| zf)@n?DOp?c5yj%!3)D(Er${|)^q5ORsGjl(5Ek{^$O7^%Wk{pHZgYYl8q}-^I%WTi zLpLb@`i-NYZ1(IsOBadAJ-Or3)FI2UqZI^e?m7*2R$Llo%Ava~mXI|(@5*{ZZYFVaG0CDE-5q_YryFd3y)ORjf*;vGV24F{li7P39W#bl zIFXpjJFP}dZ?VSXzRrqDOqz;JK^C>=X@*j5Z@l-wk|tjS-+(9h;9~F2sU;(9rxO$( zz78*f1>*0sCYdSrGhmxtSrB#>eNS2gIq?p(($weKPnBOe+0<-%zmOsP(5j9T%q&C~ zm@=wo$Nwj8!3h((FYu~ew)G$Q(&y9p1?ml1Iq?u>BpMSbW|?D0l6qjZ*U5X)QPMrS zieh^P#P;BA0keGWYlTz}jexzK1qW2a+ler8fAM{caFZ5{%H&={-~BflnYe8>8Mb}= zmkA5i9~L{}WQ2rLiC-1FECMs32D#$1#>=6@<{j#!xLHc(KPxO`Fz7u*agmT6A()dD z9g!x@M4&_NPB#lrh}@jq+jOhF{5X4wq~7g$xIr6u2F)DnQotw;VVEi3Obsq@J`^Us zQ6W?c3>8{LVX-NiB*v4xR>PRS7m@lz|0Aw1FVY z^kV;vg(a}dVE!?sn|)_;)}KVXY|DmR3XvATjEjMd6l}vQ5FuU42mby%PPUk3$-3|b zz}lTBiAKzMl&ODg9|xJr)S z4cnROwm{xoh-w2sUK*9uozorb8ju#s{$cD#G6HTt>{cme;3`Wk+lLg21T5cSP7)zO zRGYc-O0L9;y@GuL0gd-h;11DoKp~shlv0ifDT?8X10Oh8L4`u+4##yrO;u+b1+0%o z61sM}f0>G~33g@%4QZLzzf2{1=`T|`E9qPR%T(aMnM(IxrgAl6{XdzCHIDcH!&IKV zK!&YTRr&r`rb2o4s4T^_K{cVWA~DOLFv0V@vtzx&s37$hs`L;QVE%K#~` zG^SaCtq1=?m9!WNJ@h8xRN0dT%X|jUR7ArfaUc-b4EVoL1zKz78>&eAUHk)8H1hs| zD({s(!DbQy|3VdEk-tzy@*AprQBQtGh(*b~{)H+6f1wKCe+6#B{wGv9{RgTDr{DGd zH&mIo{$HU=PsBe^#ccHdhALnn|6foAE(%BAB;So^mwnecDMc&{sV-<$a2q3UOBAP9 zv{p5*^@*7VbX6wP}k2EWhp)Vqou+gVeqYf;IOBB1(kK$GvrOT9k(BH;QdYh|(yj#W226IUA-^NXG@~5t*2^1g#LF|ST zD4*(hMTSGs9lrs=IBLYI9;Zh{PDL-Ce{>E5rVQiXStO5we`k?EjIX5YD!ylt1XIF% zl~)Io0Mm)?zh{wRKhow<`*Qzn+=xc~ZQRVoem8Es>awH1%w7s5J5BM)u{FONH`4#z zxarKX6^949)1~@n7AXbtzZ*A`e&3B7`tQcg?JQyYozFdV&JFqYEc%+$pf~6gnl zg_s>L&@W+yW7i2+vR7nROaO{&3v$I#NOcQ z0YxKZ+v)wgL$t#R1GG7%YPPNPdPpt^#=>H{{C*;Dbk%Qjqr+jjW`dAXCd(R4`Wg3$R^GtNMlNvvf~GQ#H1flnq%TS^TL%u zbSZ`Xk{mx~f2jk6jR6bVm#5^~-uYPsqFBa4Ylc=Bg9Q=HgaCY~#KjadXc_DnIVv5Z zWKRa?iUZe276B%xYCeKUwDa)Aa%E*IVm;GZ%&o>nRzwRZ zwWx@@@FIndgGy*Im7va;L|Cz)m>ko`N&6C_@kT(>set>dFn^A_#E-3}9i(W*eG9EY zBA~@3TG=_c5M_ydKcwOq4wH5+s3rU0hyj(U9Q^Vzstn34wwH}NAZoURUAVLBSGuU~ z1q-8Dwq}E$vqiJO-wda%kQU}-OL3pVXHw>OgSxrmeC3$YvaTb}!X&)`QF}46eX11V z5J%P|f}4)`yg5rZF6f?pN8J7IlT2Crf`5WOUEe{U$bW)9lr)W#g5TIi6n8s+SiLkT zsFm9&*&3!@oNdjFYG+fHCo@1AuwXydaOti@EX)TjpnE?>)Ej;@o62<{I{16Zi8U?t zBsLIbUBJeY%adhd8~g_(;qHMoL-c33TN?n+XCO(F+n?=lLXe%`hh}~JxVQXeIT2ij z-#wpu#y-Jl5zm{lr0m)w@qRKVibT6GYj%`1f9HqEir~ayECZQ)aTV>^$!hh)vP08( zrx&wlLZDhG3x)@KC3J1j&h7+NJ-pt5R7z{LK~~xxs94!*_q#R^)(tK05^-U1$bGu! z5kis`l_5d2b9G`bv`Je-#u|M7K0@zsGoH5Jm%pOTXFf=)21qUIKqyd5E-+kmwCIpr zA3`+RT3g{;ui9An7!>!ixYeE*x5)xUPzZFBie=JQW;y@l#pbxQefngpMwng4(s3Z< z@Sa~28sPF}Yz9c#bwZAJ747HWb9v4P6mJD%5T(#U;`ZsaCSYL*v=GnDQJYsPm^T4^ zBb&2^Z)8JEF}e468VcYqf&%odY_5c!g@+DHdzMKJ*RJUmz7gh8SkmZ{3OZG?`QA2 zUYgnW<&(3dt9H8i?i}TyLZTtWYt(Gk{5gD9ZhGW!{()Q(rgf!(HbpD&Km{INC>mv= z7VdEoo|=8a3(#jyYtUD>{3qMxM}47PZMjS&FdLplyNEu?jgR6jim*Lpvd_UpST3M7 z4MkMOPgH3go?aD(&02k8I8weO(Ho?`p1^~k4P{Mi4Y8Hd>X#& zn|jOtt@!{A{jK@LxPBv>7RNB!0wtZIp?R8DrWF@Z@>-e~7_s8S=;J98^ z=kuN3C(Q4iyIcC-ZjU(M*B7MjAzAOY=LdJ_(|Yf_y!EghRny zx72T21NQ%mtyu~GkFA*!tBdLrZt-lpw4ppinNS@3%S zX=IB0`9o^m{~6}srtAy9QZSa}E1a__++bod#u6U$Yt1YG329`s!Vjp-zt3R_}R zNcXp@5{koO-is`NIx;Ffc+Fym2H^{~W;nx9LMKA9?o=y5ZB6Ci4h4Krwie|ak=bWX zUH5I?nwvI5XN8R-i}*E2)b?vXrKWx@$sBUM$(|A@+1!avVbU}$uJtxP^);n#=ZB+Q zg;j>HPRVgx69%-%;y(6s@5v@>gQ~bN0y#yl^a-0o=U^jlPecV*N4Yj)^_43Vo{Fsr ztLW}eE^0ujTt0ysMNwyTnqMZW79i#qvjH=iRjKAY-8xl=DPH9RO)cJp2~oZ{x;1`| zwHjuA3P^YBXA81mA}tb*EZ;Am&6FfQACws%iPQi8iVob-S&AjVyHCS)q9?Z*K4{)1e>|95!R>AyZ+Xdd_#e zo(s&| zx9!ydNiN)InBNz_s+pt)&t9RE28*Q@tg8kO1ht(MNWK_Q)Gn#}EI>KZ(YZ;B@f&!S`m_N|VLw};Fdp;%Y*)KKjGV1B< zqO6zQM?Y3Gw4nD=6Wo2zEg6#=t7_81wbyaZwPsXwp=Wi9xa0;Umvt7L3rINe95-95 zw#$boq@YViJ88Z2>E!BOICLobaGIsWtis_LZR5IFUL7P)h{2YymHS}m4bDz(Fxk2; z$_-$!w>}#I`{{aPZ|=F{7pxxbptj_Ds|9S*Rym{T7_Bh$m4b9kg;W9c8h1G6qB#A~ zs<~5%UH>TpWwU)3ftuC=k((juO^X2L#g^LK_v~gRN&)^nG2yHRA5Tc{dzt399JRVuO$Rm$Y0)!1iq+n^ad^m_Nb-JdCXE8c=86yZ?4X*O z;;+F`!YJ6TUEvbRB$td4Vpp1{`8GJou25Y_HdV${QG(DyujZ_k3A}RhV)s?9sMSd2 z+epR#8HKtRDjfmEYhTdu4%P=PKGz<44sfW2gtLdl9wccQApRbO`ke{Xb$9agboKkU z2P4;;9p~%&iQ#?pcf5-UE|O|SmHSr+|J8BmFC;>o2xR_@EpcfM`0IQlOH_Q0fys}G zn4&6(MxEJqp0Fj15F-d}b0fRDXb+ev=bNmEe7=F=i^ddNsvl@~pb*W+deE<1pfF!* zcgBrAz5a(_Bg)u>o>N+?rGg!8I@nw@$^LQUgbjFBb2%qSnR=cbZJxi}yq%o@b4uYU zEZ#^7@8plYJho$MGu3RykDCtPKGX6RPZO7X>4@}pL!xsQxkf*&Pnv?tv0QSu7k3je z1?YKT5Zr8bOECqi@>SI9hsD+`R_r%?-a0;gO(ripLu@d8 zpo`Us@`50%3_JZmXJUv45wQwv5$LG!kvz#a@m&bvnhvy#n~`MuK(S1A2Y;ofneXY>N;qMfNN(N$Sq-faOK z{6xqsGbP+hD6Q7kh6>J!vI%4IpH(P5Y87ALI(gKlqROi&NqbgWQ%9VHk)^W8iJ_m3 zvTb1;k1GR$WKSm(h4oX=t&K$mk(Tw*&`&?{snOBDm*&|`@x`m?9`rKX>!YcYP4SVN zm-MMi2I~+Yl5)4>xlyvLTkuAGO~;q4|fE^vwTLZ(%$SHSzOuvIa zxX;?h^Q`s9$3!=-e-j+YytY9bfEj`1U`#6FZ6cV~Vqp2|vu%Q*-3KKgdNY6wj)z<* z_*OrLuv|z;HVx;zjm7rM0$GzC=-Ot{c&wQK+}{qpkZ!|X;EZIfe$@yzLLOos70wu! z1iCx3xh3obXW-unm)gfExp2b;FQj0|06)X4$0U=*M{Odm2X|`dcE-?i2kVun!7RG6 zq3K3k{A9rx^NGgF9c5mbem{M-#3KfHh?aom=n(5xg92Vsr8zX`Amy;ssiat}j_>Ze zf&Z)MQMt6kJX@Rb-Z2v9HW9`njdxqtH4-24%ujn#PiZG``;wP1sTu&x_%Sec2xR6O zuuo%LIUUspZBe;2rcak=X6EMsd=o6%r5Zcfzq?Qh9ypgfBAm|O$1fakADTCy-zWPQA@qMDXbz%0fJwv_ z$`1A8AX}50sZ6~E6%?c_p%-Pph?Ys%avXf3SW|;MJA94M*Z+)DVZbSmXV@jkmoS3q zj5KD<7?1Dv$O9_L*Iv2;ooMesRadpC=`IVwVJf#@jdU;fgd&-MR8`25`WY`Yj5F+n zGr`9l0-Muqi9;$fXf#dDvSe%(xrE5cXFV;f{Ax_v;fS~t4i3}PNhFgnkEGB0-jDjy zO_D}sdL)1W4g3xVfmQl(eh)~Y#O6vx+eQ=nsc{7N#cWy;ZBZ92Oj@=IQM?V|SSbT( zuLq9@-@_e=$R5WD4xMmHl77S$5NI+bb!$%&u23Z4`upfktz?48*Se}By8(8fq(!KS z)XhjwPw{CB;n`hnm4E+5FeO;vx?>f~=Zc~&5;cPlLBNA@Cjq+raKh2Z4(^Y) z<^66KwcXK%w=K8-Zu>Qmf=b{NU9W6^7^JfvjI3paY1(a5T=#fnSpLp_wvyAdG3jh?GNL zbLjB#H%xuYzEVg04S|dW3EYc_#K%~hGpZn(CR^%1w|Q_>giY2*K?3H!dqVR0`*cfI zEn3b@>-r}qOOClId2sk`XoqpcZ9l0nd5DB!ee(7%xC&~U@ACvn!%@q*Vi3e7ng^g>p37)PbOs3nzfZJz zSXL7KWM9KL@nYSfbj`~#V<6Hf1{iiM25_RqQXqFs&Ps7foE)|p%qLCU6g;A1foXhJi7+hK4HP77PrReTwIPuU*X z{JQR=Ivr_p+fyMRUtWr_4c7Vqyfd zq+4>7y624@b~Owp_}jkAhK~Dak3)N?c%%e)n8c@OA8$=;E>heR$yIM3AfA@w4O__P zAyOQ^qO%qh`sjt|Gl^Z5-HwPOev&a@i7mkbTS3xQmP-g)inc7JYO|LdiN`hfIUlBw z?ro-7K^r6Y9|cjB+!im?y}o63)G!MMO@K^lswW%3OF#auO^MI`yEZlOUu#or)!Ssv z0fjI`ymrJY<|+V4gB)RQn&nVbu!Sp>V!GK+XI#vH-^)DqKSTO%+vQev9IJ4LmLLrg zQb^a736h$>&nSNif{HvMsR;^?kb>E=*zNUJ!qndeEdo}wH9;Hb{?S47#|Vtcv=cA; z4Yj1c%e{}ZM+D*eI>%FnfScBypYfOUytgS--oG%)rzu8bf! zUlwHT3fKb9>quIgRD3ld=$%Q6flx5qrs9@4YelF$7}XK%7E z?zenpnGZu|<9?1-eKuV3=|K84wbQU`wIxPsIDa(;JMI3lmJLl3tp#l%?h{%g;;c-Z zvjPzAxF!2=+xR+-oO=7_?8wGG@OEvae)U3V$3~u0-|(wO?L6JtcX;iFN}XK;@pFDa z@&fN{=e6-p#asJ^gqUpS9O(_NyEq?|xQDygcs-uWh7ZzvIK- z72wt+&gC7@8+&RmR9YnJ&^$SE>F93I{K*fy4P86ZzGR>|JyRbylH-IOhfH-7Jc--3 z*;mWHH9Y@uEw*%UZ7iBf6JET`FiIiWtg`Q{LTeGqsNqWe*I=}B|7w8Zn|)G4UXRPt zL*o1%cy1oMExjdn@UZN@R{{14ZDF!KW%{$PfrrP|LiooKgk~Lo8SG`yGT>ld1Ej|$ zPA^mq6^HYm8roDcTrhfq-o4Eiw%l|+% z_+wjKzMmz3xc&mBV81_4$+Zk)alIDDjY-~W-68Kk^Ryh7UVDM!tbN(kGs@js(fcGxs|H^-l?@TrD~@WoYZ z{E_17rUtAYR0{41+|J;-&D7svC=y%!Ee4MUP+6GY@B3upq$E`b(YKPY^^k`qi1O;P zN-Z77(g6?I5VpKRDuW(CbJ;ux4_C;`J;fe|bC+A^E%9|YS(_fs*>gv5yj zc^;$Oz20egf-t|Zgz?;9WBydMmrtcw*J5^ljADgD$f~}9H}a9XO~rC=bR>i0eL94Bg?)V%Q@B+8Jl zlORyZE;^;B)b$6Nu*uf>eFK$iP8kYBuYGC#lj{!UQs`8vu-4+u}g=dQFc?<8@;wcjuMG$fFykF~`OtEa%I7Uq~|* zF78WaS9g^8u_ZxB8k*8coU_sP!`g0ZbDT&Xo#w0eM#PFnqQYkK(=f)`pKRJFwJ}>* zuWsM33sgO6?-*KN_xaii*p083K)2{X0`kdj8O@ARwIN_ptqC7x1h!iJt``)^*ju%q zu|H$0%FVThMb)SbQnKQ0%k|0m`u+In)M`zlfg<+SoDGEk7F@7`;#6O_q8m(}ltkl! zGH&f5?eLT}zq1V<8xz1NYKNHf{k?B}D669SwCzZ(OPGi@i~0q&86R}k)j6C55=kXN zI!a&ygYo2a%}Px4gHe;N_`+YDl9eP?RBI|q@e~XR9ruy&;s{lIArTWf?#Uy6^{3LPaVA_9Do}@%xHc|<(WyU2>A4-kv-*0Afy8;{NZZ1J+w5Q38fmt! zi+a+Jo$b!Gp(%VCn`Brd%>X9A_#ipw{OPyUQ(6!y@yn%;DaD<(g-OF`#E^}i`wvNM zmm(|D$AG6+fx7mrXQ=`0Mk0IT>F>-4d%;q$!Y+a|Sah=kH$1TCeWLzG6z^7tY;EN2 z2_~ZupprmB5$vu)^lFE8C|_~oc39{`C8TE@?*m7aua2YXi{Q$J8XR2vr5SK5l0RT@ zUv#5o9R5%D(G8Ty!l*{dTv+*vQAti^=44+%kgdPWS%Jusrh0RoqXa1_dR1A?e_@u+ zK}bV)o7LXg2t6cYK*ffEZSeolRV1n|!1uVLrKK|@R^Gk5 z@KDVZB??{E)1?8K^CGOw!*G#+h){P)H0XTskA>_g(GRJjxFr>}MkgW#K}sTM;Uh2R zPD$+(iJ|A#CxDzpDsVn{p^Tuu?Q6PSO3-qi37i`}_IWtk&(z;7Y=yLmJL(-8!}PBD zuw>F@SHp@B*F|)xW%q;^&^(UXx7t;gQoV}XjM4Im4$i*sv?dm-&ZVahjl!0(t*nyw zRm!SHQir*WaB(#4VEN4ur^EcV2l*#fx-ho3?w+pI0HWnblu+FXH}#nG+_~3n6X^FC zP#p}dQ}V_Dz&!iv0lV(Gt_KvJ)9$9P=Gu9PRv zKw6UX^5#k07{-K7Ty>pYWmBUBsTW3f%Le@v->l(C*0MM?ulS9am6oocRe88EJ@j}g za3vu<6LGJ^1-k?*A?y334HS1OXL;YK;quM?bOshj$2^%$^=i}NefF@+yv-RJo0O}c zt0byvm*lc;AA?-7cLuso8o8r{LK_t$M5_+n&SBXduBvh2 zdL1Q3^0o7T?BQJ1SzGK3b^lviCx-1igu*%+sR7m^$IlJ5w-?M>Gwl7!}NsO1f& zq&>(zB2|6O@d?sS)J0Ex!;AD}MSyd%N{tR!!i(-szd}FDKWt~J& zJ&d}r?adJ{>GBf!tXthhkBG{V->0j>v1)yx8I#K4Y2v92evt&}9op%R@Phq(SIgtV zpwmrc00P7vSCoSkeB;YP2gLB1NC9=$1b-uxSdm}UhPz3yLgB$cDJmO&Z<;R>^N}B+ zqa7|=rAL#eHs? zZhddJr|sgK24hFB>M!xF%%iB@RDaT2@gO@a1_ke*TTO*y{&rNt<-`1sr#dX3&4;X^ zgzO6X!X^|U`*J{e3_vKpar|U;F8(hyW4Oznkn+#@~XrDYI2SNCLQ;{SGmjz>_YPZu$18;Wtxig;?IK+)ErR^UCpMFdE%Rfp|Aa|{yJv1AJuM2-RFW;onO*~!W3 z@s&Yo=^Qk-<#|Yo?+t+IY4_J1;unh+XCENr#Xwkha~zK1&*_9m5@f+Z+&Kr! z6XvQaQ(#mul|Cc9>I{T~j(!Q+&9%cR@GxOla$!s5yL-aW^uF~A4}SUMD_k|T&%-A^ zf(5dmW``C77&MwIivE2#%@s7P#IEgrWA3`B;MtTRFLN=Z)*32bh_g_X^q>@BNJ|JM z<4NNh)g*!k@@9t9a!C^)6@djN#pa`&qe)?$_JX-Nei7b*W_h;`+jAyt~E*(Zy-dHqDLW^lS<8)?u8W;S}L_6&*VZ~q`0i-R)wNTdE1_6q;ltZn#;VKiFuypE zHye59Yx}v&>*$%fuEBL3^^Cqy-8gO{Ht^3EO=l0;%(ecC*roCE-Hp$6bV@ot?$S#en_59+$8FwNEwR5>4W}c3_Zz9|&25QQz2``@=``w$S+xc*LU`^Ln_!yK z>>nq4{NWhpk_(RP0Ds=a$a3>awvixWSx4#Za+wP~rr{~T!Y_Tg@^t4_K^ou^D_DhW z7K&qtaD`-|aPtpJ32lfo$!H2u$in+zLlS-{I-uo8b+rgq#f~|1RZ6s<_1Q&Mn>QFO zr*Dl4%CE!0cr~1Q?lU??4!PqDx^pUn(&`IkXk=evLHQ_X8UG}Kjf**jTtm)r6ABT@ zmBmKB_;p5<*#u2oAFHo|50V|iAQ*^zER%zdoGRpRtJlqUO4^ngWR-S;=0|6CC&#Kn zAgm!vab!U#%kX_~7EtXsMkqf!lNa4*_Mu@(Cj5f7wkDrM{?>#icoozfjnm+q{udu$ejYAFKU+$jRzTq1@aE@ zm_gfOr8+}O9m*NgE_zV{Y-;8jJElL9_mi%%p4baIUSaO(^+pYiHGb}}X+4VN%5z_W zT%@eE*^wMp|8y$o)~5me5c&y`D*gs`u$M~d4-QdJix{&l$))vzbD2D(rex&0Ukiy) z=CA2Y(Edu1O8xPMhq6G>dGon)eMQ!mN|0xT;+;03AmYWNTUA_%f9d0|xs5 zFe8*H*Vd2=hB7Ur1&33_HXp=uYep?Af$S7lsfK=(IwdSq9Ow8F$%K%CQIzwJdBQJC zfO<|U&>kxY1xA`64Ic821QO-v6LN$eF9Gkb(&C> zHVns}rrDWf+0=_Ri+b5c+ptm&vTPX8)K{TAIF*Sq1Bev;1%cQE7b$E3GS%ZU z^vbTJX~b-j3rK=VlS!!}We#Ba@jP)+%!G<7St{4oqcA~6xjhVNnbE3qthVmAQ=Nb@ zghRBHt8Cr%fok*=&%u%Fj__jK0AW-)alM+H609?YB^zc=qC-{NYR7TFxDt$saiutg zb=;V{YvDxgWydVjO2LoMsUIF#2&=EP6CMW(c+b;{LZ4_}N*q4H`*t>7lpzi-AQklq z@&~rZva>>01@9Fx>M5U9NL-*3LG7K8XJNap71%$l%@uQdmr90y6qDsR{ZwzG3h*{4 z)son?;Q2GbpIbqZ0xPsK5g^;wU_Y7&1dXCs8A@N@tlxIs!l}~EPlHtv2QKqUD<_4{ z8`d{rYLC@GKHS!fzXQXVTcN|zO0fR~p@lGMKMxs|aJ=REr%yf_++Gd<4ukaQyN3GP zZBLkj)t8hP0Dp4}C5XL#;Kz9!iHFRoFcH|$aA9n5gd*2)5_tfprbCiBx%JMO^iClO zrj{iQLQaEf2?LQuzgNq32D@giR``n0_LV=`He=22%Z)Z8jya>{j>T9f01I1Wu#IjcaYNkWgmWt=$_OR8;O1%o?RiJF%;``Y z0p8|)^Q}iPwxY*@eCN`VTgR@O+4HCc!zGj@s)6w<{dM*_H_ZU?{9qsn#&^*DZX7Nj zlmrpp{XvUUIXWo)14B1D0#X3oF9iB%R^$0{G9g$Vo3P3c{Bty*Dq`UE!2;6kV}Ghk z9v8RTRAkSkm~c$2-c>q0JkeDvTRFd9hMIcdy zL*qP9mHem?0k}@7V*h|xb`eZTY2^^rkXFD&VIXTe=lprG2{YqU1a+7XUsm{TyGKRT zz?z^@mq!Co%pNR^I{wlC!ak~01og_k0)6!mnLkGr)RI^MFcSe^gxJX5M#Mh&!p=#8 zz`HSL!)}nTfp(s7U@8|2c=TTs$1>{4G5#@`$^AJ>v5F{18OXYR_T3~RspB)E0u@WMg z;v~qDSD`8eDMdn3p(ms3^+Nh8^RnwFr1vj?Z6y3@!-}vbscU#?_{EBPtC}XW`7axN zC~3etb+YKRMLI<48+D-}3o;jky=jyiJiG7Ca;p(%QaBb&T4k`44AMu#{1fe90_ zUAUY~U5Gt+9+Sf8D&LceN^Z)EE692U4>mB+6Y+z_o|KiPvFmSZ7_tt0f-x@=*dr#` z5%uQWEAhSN-01^ln>QVIX-INv#mTtU2Jls(9pPUI#n2zUMn3li;p`#Ke`zQ)$P7gQ zRv3D7nwxdkHllIUHV$m=PGz-z{p3SIWZQ)=OF>E2tPn@je#fG3e|Dts9`y%$MHnPE znI*K}{aJuej$RvpFxF;ZwQbjNC}GLjv&!T zg@!LUs60?S=Y`qD`D~Q?{2me3E34gTSq=^F&PWjbo|N!LQ>r96O}g0Dy3ho~FB?k+ ziGITRdmxjv&o7o#j2!Zc4G^7FT}BW`;;uL--sF4u3pCRSsiDLkG6feptpGo8pB;sufp~FF%fwpZ0%4bV z+732HnV8#vZaVk*F%bk<@8<{upni+uw%UMmt6P0d0B8L{zDrL}AsypIi|-wRPF!KN z1@n?521D!m+|c}l`eF(6eBPnf@_INnm=hR(#2FSiQP_^d*1bxCTGcP=q59jK*_vna zouPCNGs-2ba?AA2j2BfJr`eslKNSywg|bok@Jnf8CfI(b=4WetLaD$f)qCd9wjqLM zt-Dt)`vV8>s}LtL#Vq$+VhtUc3_K>g` z`F~CzBd-UAp5h?rNpweJgVr_oy)Me`2$ebo|wm!;olYm1nb0_rz{zP^@-=id__s45;{>R>)e}BH-Bm7oI zhi|t+)T2Bc9Q;Uq{~qye49|OQ)8SUi5A#^gt zrfFN`>Ocz?RkvpQ@3qFE%>XlL+e#C=_BR*T+f%i(iFAJY_4cAIj&Ki-@l3iL)^(${8~N$&3CgqAg{=3B&}QaygzX^eMiqUoCUyT zF!{#$08>w_xR!mJTh`L+!ag^L!|U(l)sEVYd+rmf>nVG!+)Z+!p?W5nwZ&nZRo7#F z$X$?!Q;a=s^bvN0;zc}}?|^~C*m*r!Uk|{^%4uhpn6r-y0I70tF`3o^0Vea-yBDi1 z4ysJQR?nA*m-CtLMTejL_WX3W{;+p^)%1)`nK-jJny^Get+qt5d7ic!KF?!Y5}{w3&L-;w-J}A)I!4lP-!^_O!yeg z>FMWjQ1|9;UQUxwPh|O3-Xc{|A$=|UB{i2kkIrpY{E8H6W23_*f2<# zN?g23lI0Wv&QM56Z?rh4lNCX~7yH>7a&zJ=TFrDquct~0L2n|yV4cLE53g$3_UOLY z@>#S--Quz|NR9wcUgJb1J>p@gWAb}GRW`|ei=m-xHJ5`kD?*kkv6utH%UBaEh2t5U?EH<08n$LE zljnOIUiryuD9G$44@3xeWeo-`2ks>1hs*-KK`%6SdOeQse4SH^p&=1#O?M_foAy^; z6kQoLD^ZsKJb(J;ZK!AGjW>qgZg*i<@}`j{G_w%j?5|60i~!qt&D|&!Bzl|zfY?JC zfIpGi5Y#MWbfBHRy8bSxyb|)KCt^DGtKs&X<)@O_%CsAj5Tfm|Sp|n2XgZRy;LPR- z9|3xWd-ZwJb0jpv!JM7P5{L*^=_)w!F>5XA?sp*InR7b=uC=z-q*0;PbepR&W(S;) zn>XNPM{;uD6HlREr?X1>oy};^0p6#dUiPd0#>X|VT_a}+3G9lJy2S93{Nj`H-!%n< zItAsJ!S5RnR&Onw7H_Sdj@|ePN6CJ*LAqJ)Z{9oE4?bWwEeCRb-STj zY)`JoIu>p+_Wh*&gyNP7>SK0@f_p(wXzA#rGw+-k;mX1ddHbXx*;{<%%*BdFXs{&@ zMPf|dIlL9DPyqKY-C+@c&S+auLU3Y3|lpR6;q?|6<4|31y2abx0Lc)xr=)&Z2PJl{W8N;|i} zg_gb8Pav!V=H%Tb6kLfj{IR1t%5mhBa)#xA!s}LF=|c^cWIdTdPJQ-sbRTq;2>~vi z!KHT@p-0^fA&+oi7)MVZ0v^ea$*)#pYXo{pe5vVOUvLK+ui5vM~y z!TuOrDn_6p4G^jL{iswdxm%vx?QW6lAZ(|~RsYd6K3`U}T(@>amu_aXcl)O+>W$Ma zkZmKoKgCB@svZUAUVW@);a~1l{dVA&I=MLV1_W~?YMm;}me|83tv6>3J*H} zXr;|tBikxjMQvQH=y;SpB14H<_R~z4eq1VCBhwCA3-HqlO)r7(3}GPH*N8qVfj5s% zk~5+!IR8{l8>c-NVc|?I=|?}oc$-Fjm@wH!Jc*s|9ukJd6TenveA3)vno-*S)edJX zYe4uTR!pe+g6Q1d({_X7=`sI|7HqLKr+NTYoudXpVK6-@ui$mteGF~0z(9;h859b* zb~)8P8O9-o0Ng2wvem7E{}LCAgr*5Yw}{>T&^V#%VA6GMrN2VAn4u!JL}MRij|*e& zGJT8>Vzt9pNqa&%5{ZOaAWoNQBqh-skFkqib(pZfrciBJRn@R2^L|R1S2}gWZEO`4 zH-5ld7ZbgL)ba)l-bgf1&^0Y(QVD7frE+z;X~RO?OmlZi%f!gghd`Aphi%r-L^~g3z6TS5wP}nAn?nT-tFOY63M0BD(FConUJ7{ZZesPj*gI@OvP%}SC z9W?^T$|L?vaMU~z@shXhy%r#&5%;|nSdPK&l+s8CNsd@H8{8^$fjXlR9f>ryLpDgp z7SBnRm{!~H(ocCPZsNuOY`I~B&oJB6O3 z2$Gbj8LMMnXkB$OwYGrH4($vfL(P$NrJ;wHD~Sb%9avR{=#puXydcH$NbIliZHZjW zHEWV92Pd^6RhTE>nUlHj3q5*9ppn)&VjZt|eWxYc=u8UKNfk-cUbEvimPKFK7AYV0 zO(Gb9Y0|gmP=TG75UgQI1edo#;bNmYlO+E6R^^)EtHfm)&SfbTm@S^t1AEKw0bT?C zd-*MiT#Fv;v;wCjA4nUlKP@m%4Y`z<%%F@~MEpFzbs3GfQ`wC!kH~2D85R-nle(%cKDcve>!+U^8MFZg9KsQS0gD^vX*Tn?St9EqCDUyaCQLTjL&W>v#JeE$mY%43-|74?!M~9C`Tu!FK|yG2GZ}7!HW_8W4F*S zQa}KSh_OyAe@W8)ixw5uFGOEI)iw26i`V4dq8h+V;Cn^p!IC3w5NAS}&XTA&e+yxH zk|ShykYP0A?MF8#8@Fu_pHdHH`HjGg0lV~#B)rg=6(JbR+ebOMPgJ;!6NAtm6;vj@ zxQZZWVVP&8bntO8;e>2_k*Mjsvpz8;Q)l#bWUdX~vR)@j|0u@i<5%CRKiYN4x%3PY#wsSKWXIHNhS@;59fd3GLGKPk52w{u9yxK; zJ$drE{ITH?cjBfuIP1cyT6udltU&_@%t^8DA*B170ZfLqB$+%4US=>zUX0LJG%tx` z@#JLsJ}*M?n{lB;-lf|o$i~t z&c4kG=Acj9vBNIOdQ%WH$?RTSC0cLX(pWT3aikj*?=t9=utoSp(q&Mn%7`^2%9ogh*RHZ3irToK^+eGmh^ptYO@YOp;_4+EW#!@ zP+MGt6-Mbm!PaR#xSA|FXM)Q1A~A;c;v_2&V$^CMzrNB}3I32qoBv$NgCQAG?*}1H zX^m(wgR^@*i|;o|dx^}@bZEL>;G3?=VhE$Z&QBnw@wCsOp`Bd*scXZiq_vwDMS-Y< zVRKeIeH_ZvM-Nf%+m2yl?Q*)-42MJG0Wf!;49$1v6LN5JpLz$L`7H(V6 zW!tuG+qTVBSY_L`ZQHhO+g@dRb?yD0)A#g^8{HB87xF_!=A6$QgV5C;e1_oH&m<*( ziT9yOo5&bAw2mlcf2(vg4spbU*Ik6JX~1?k^NcOR)^y<|W~yijM~*h$ux64g@h($$ z(*o^0KR57NZuG!X!!2vQLeN`1sK95Ak*iXNf6m)3@Wtdn-|YDy{o4_2tJ9rypiFd4 zz6IM!G1blA62z2C^&APc&d|Fw%ut#Rjn3ZO+?YH!V6aB6-PI~Uvd6{O(OOEh>Atkl zOzxIunfQ#ujvTcU{}O+7RsR_d418sE5_+=tC1nv(C+T4DtiVKwZ83(1EgTS&WtbqB z%7{(Jyl&F1+K!Foq^Y=`&7{Jl3r0ixa#1e~Idia)x?w_PRi_lgKy?HK-yO;fxjs1t zX)ZlF-bwoV<2z}K@`ge0y&AuB4`B27>srY+o*sfICPf&Mn-evl=i%#omKKP~(+K=| zr8QYIeMkYRCb(7|P-Lg!x~rHiSx#YsO%qw$7cVJvK4>N$ttmy8pi7R!(luxwKyfig zr~~kIZ74+_>}7Vq0LRm-wqxX7h+7(e3<2x?nupATC$%gJ}FiXA)+>lMvN&tvj z{8!8p*o(c5UjJj2m*A; z9D>q_ur{XH6ExEEm_JIAH3li5PKQ*j2nmER`gls+{YYV1ISmx)lih6><#ijV{M}T+ z17aAz_{y|Vnx@eH8M?@o>hhquy|yZByubQLpZ4mJRx-}8x94vfDK28siUYwU!d9yy z7x9%O!96k(xfs&om>p|Q2kNbU#*&jWS8|L6qI(rRrlUPu115rhDAn2X=$p@)qvim#FQkD78e;(VrE!eR_`BPJE(9ke<`j zqdq7`6r;$S=V^0V7A7f1Is-u|M*g54?vqc5Lev73H0z3Rz@23dA|zGgAa3t21`K|?USj$ zgVLp!XvTw~&@mt<8D+a+7HhcH0TgR6E6MB~1q*wGGyM5ufq4iPtyn4`P%tdsFK69j zd!JXP6Gd!hW;EGw8L5T2=GbOJ43)8brC`I>aKVZlQ?NY7T}9EBlAc20rg)151*M15 zMd8Lx?oQAj; zob@WL@%LJk@_b4hi?Hn~RQt9T%>W>{6$HE;JuivJ4f%mly*d-{<2Aka4cpz`?)zsv z@Sa;Z+ncMer`N@e|Mi^yiR?3(YDy4zy>k0AfOAp#JKqNc7U9AWw};3Li$R=JUjSK~ z;8)LO86M9B2@&)Lqhc{X>;;Buw++L$AVn7l<1lM_Oup}|>JFnc5~d0BvpXsl=7d>M zyc?g1n#279SIuQr^j1s3D?dkHIF|Fc4=!?Ld=laufH(#i6b*^k=EBeo22$U+U2dRp z3Qf1Mi2?G>WS@n1)c^JYm_C#kQ zBboQqBW^MV*Qp#!26%|_L7Hu=AWLLON#cP_Uy9U3iZC=bY|hsQNLN?BW?&!acsOB0 zsgpb5Ut{Q>gG^U|we8OW59fsMAmeW^^12>LtheY3WcE;%t=GW!>4pvY=-R5TH$gF1 ze3FNCeMUhoio->P+smuPw{R4y@61PxGTcUgjvZzCV@(+8wFVr!fMu- zwm}@Iwg=LVvWFrPOy;qE<^V|FM4)5{25gTxmi3y+rxk*}gC4taF}_TG(}4Mr=Y?$(UNfx>_VrBxg@# zBINFznu?vcq^R7O#WfeaR)hja(9cW^%!wk54N9|PJ)M`5FmiGt^alx0RW--g@ef#B zW`~xAdxMx-T)JVHnqdJ=^{=N+(M{^g9Sr0a%7%K@?zTigBZ}dO ziPBMtp6fzgPAWUAZ$)-0I|)63ithAo@LZUnD`Ic)A?w>$rilv!sl-3e$IiHrq;z2V zxT4@bp&qL6UL}5ExU9=k!;vl0f}V;#6ZvSNO&Otkp{(n1-$(_|QUoJ;RY*mzw253L zYemhzLTIT-?AUao{IRSnX_b-6mNf%XG_*XJAz6OGb{nduT*9J?!=YB0#>0{LB|%{+ z&AD+P$jF@&S?q-{u_9fP6%}xeGA3lI%6y}NCFaSY5QN+Ey~Z}l04mkoG87RCe%)cn4N@ScO15#^|RO&tzY8lZL#(pEx>|rxMHC@K zAnU%p_jK>g6e{=uci3vYziYqkINx?N)TzM^1prZ&5e1Jg`Bb0^#Sr7(jALP{C&#u? zJPqRrWR7Qd1Z!=d;5>Jsm=ZN!M}XKT*b&FK7VV1QpP65?G@}8mf+V2v>A(6s&rUo{ z2G;?|DIG&BJ?EpBZX*#R?8YBocXFn)&cOf^WF;;=1gtr~13{R+%U;XMv3%~@kpt!7 zf}&GBct~yUG&Cyv3LYPp)%yv-r$+|s$c1DOuvKsX!Bas0Ba{^N5TF;y*G_k|nCT(X zFG-f!0e7Cund`#0xBnGNtpZ?#YuJhPmT$JF+HBG-Uq3$S9;hR3pyS6U>Hf+HT0Z&l zNu;|f+V66g-QkEYC!8P#%JyDJ;7IoX<$Aopzv&}_h`M0Fa{mLAC_aS1Ai?O^O+`VF zr!-Zu;Q-}CO-L>hNzT@^Ifze3*Y+)EJCD6CVjxeW2wgkc9zgFhGS4wMrlBT(xn;|_ zmQ{ZLQp_?21o7lRBSiU~pVPM|W75?<4C(eCpcFOQ*_rJje~4c=T8K?v2M-2vinm>V zm>F~U8t65L6BS0c50<~l=z&+Opu=j-UOMcY1Pm|S8a=wzdDq54v?$P@SNEsR?)qfqXhwsqdG?-dx$3B3HqC8uAP>8`F zgeOkbe@eO_@u18@@g{ovFE2tc2mjxA5mMoWPcLc4}V#K!~XY}t?|0kXkgo(M^7SdCK+{gUkWZ8Gv~3YQ9>E* zZ~sb8Vm-l})fI1qsjpT~PkDKH;1GuAttW8f8$4r#)NzzZ&sIetq3qVbW9YyHO69mXpVEqE)rz{rO4ZwxOR@! z0oy|OsFIZ7JLQ(2gb?Yl3nSA-BqIrytTj3T6(yD^hNlZQRy-zc50h`9H$QC5o=&A7M1~BaEj05k^a=CJq0aFq-{uVZ<7Hh28=oEX}xD6ESkC z`YrB`3q2rh;Pxo)%u})k2uxt&U?Oq-cXlBg;bXxEg9owYlQ+;AgF!!Za*jtLFm{k( z*=r+#?Hb3bmSI)8$wVcBeYQ0&8L&9e3g|mXMWBu+7=Z=W=YO zYofns_=+T~V_+MtP`l%7Fth@)Y9#IANSzCf-%R?>bwUN0&2b>^Q>+chJBax|;K z1=Ve2MX|>ogl|-ulp!5Gn3eIfg}9vl5bMC-29m5@5$AejwMfC$Kgh^IeQL`nEyKV@ zF#a(gL>l@ZWRxrUA7sS-gN#BFe~{6;Vxun<_skD6;_>-GMqIszM zl)sbM=M-opuep~!$m=pjh%3)iO8@Y(`-Mjws)LZl^i`qX>s+Ex*`#>txsHDMO+m*B#-U&0wz9zR(Je3b6V zEslWXGoAqDsi4Bn)QzZL0f+iA>$D1n({u8>M+RHot&R$`r4qCOIj1cA7*bJ1m>WSs zU$%U(-B}I+9T?L!OwH$cdYPCAeK2E1fSH5?hGIUVI=kZO-ekbs1CiUxZVDM{Mox4E znR5kr4zmrN484 z{r;xGDa?xneZ2q-0i%BKTYf#pa@}}?`tMP#@bZJOy6-w?vER>^;4=Y#hj^QBvs`a( zzJ~d~wxB=AW(Q*VU(8pJAiq8wV_&53Zsqx3vNUL}zDOUx8`=H54Y=1%czY64kbzU5vjGE4y@c+Z5(w!Qtc|o+g#tl|Y-zkij4WZkpJit_ z-CicLL6`;s&A5Jj*->Bs@Ok6$P&Xk4M-MuN5IqhJZ$r<2=#LUC7v$@kp`;i$s_%hu z0DkV)`b#F*4f~VO_@fx9U+o2O1SnDL z(OzGsf7ni3k*0<7Q&jke!5Ogzpd74Xunv_9-$@&?%!g9*QARR_3osDJXyDF?!?{rh z33|+4?ZxxOD6OGboNEQGzLHkcb_=-Pu;BKSz?wgh4^flL~RmdTRk z2j>}Dib1KQwu9iyHU+I}m?FH0Ephz}tKGku*8AesgkkcHtp^eyZPy%a~MJb|s(^I=j2& zErlKxc<+0pYB zwfME-r@1@cZ(euj9edX%8-Oqm*qrPXqOkILB4biW(QlWB!*8X3=7&pPa-YC1&lA5c zV^c(izWz`GpfeB}Nr7C*9nx#8ln;r+x{UI>xPzr`zdhjD>H6AAj(Nsz6nU1Y(0fL) zFLp2We{$x>dMm}~_kJIn|9ySkyFE+&8e}K8-J?$`Obq-wM83bgeGTIwk@s2W=i(q* zuKpqesCxwVG}P?vt2TR?nR-e&@puT99=d$k9?+i2#84MLR{#BC+qtrqiLiZE-3(*n z`WH@K@5?gV{oyp&(-mJM^dt0R1)PeaIu?Ic?@P+(>ntW4Gw54+IfBbT?v!nRaG&1$ zkLzuh8}lE#I$LYuV7$NOtq=G`!DhCS%nJjQyeVG`$DCW5J)(1*KhDhSLg;(^^#F2b z%#Y|r-9cOuD0gziH185xTGor*VoT@gAMnxPkZZ1R#VjX=5ToZt+et% zm&xFLVs$;qj?+Ut0J`Ag1e1=+|1*}|BuV*WoQSKzyVY*Y)*t-PTdU3P6i8>!6Pu_0 zwKMz=a`ELaOo=`yaq$xxHG;hcGqsz?iH^1l&A-kb&i2dX)}QwFU$L|LwblG`^z`!l zy82oAnz{7VjkCEsx;pw^-y%KTt#bTdi|;p)@;C1(!UCcl_C5%|v!ilh7C;#W)}!>p zORgn=A#64L&s@S1NFOq`Y4;26x!dY-0t&0z8{O#%wFKS2VXRbN4?qSX}z4gfyQgSsHl{eLHGqHaDF{Jf+1KBrjpp6R;1F_ZNF?&QFs zNjGQ#OHiMMT6byFong?A%X|OHcoYcxGKACD?>_i){E96Ti2K9rrM$weR5`LPST0CF z4kPRR;3BC)MBXfQ1}9TXLQ*%T8{f{MR4MF57_8r;RVB*M*XQ8zyWN$U-q>Tl(`N;z z|Ai}nTeif+|2RX$IW|RBM(lu^9I~kuY+&II%tt!_{pI~|8Pm~mE-QK=()~V%(I2D5 zU8)yyMS=86bzv#XOVzH#jksPYy_FEF6&vyW2odAc zN~_wTSFJH7BZqxY?DEo<^sCX@TcsFH32|zIqpt(D2^WlzmH?h8Ir94-*pAB(cn~3f z?F6-cS$^4oU&dz#O9e6u@*$vt1%#x2Ik# z?=h?X3VMWq8Z$Y(^{`(+?FV=D%(-y``P)%gEkyoigyqTcUzTKEp zc~|mXbt}B!1t>^3K`6YCdOmn@Y7fcIXDzNx4Odr1-BQHMf8;CU$EzIJ9ey$N4EaiX z(|3{T=ScU$tN8Z@Rb_nj_)<4TapX${Qy zC__rBG!LgeA+$)}j^QN767spWp>sU05j^^zrZ9J-zK?T9kv9?CeF`+Gd)gBhT3B8Wa3N}MNbXJ7%`)+D5u&D%IO%Edb+Nq^P)hb@ z%khia&Bo^jSeA<{256t^c1b#vg2pS@D@#UxOROJ1G9RA9GY1C-#M{Y|Pm;YC!GV*7q5;!#Cs=z^8q*b=3y^z|nTGRb1 z6OMoaryjJKE%mZix>dH#sTdNp6$xAx-viN5V9b%VR2H8uT8k^0+ji;-4R4e7b@>m5 z&eE+|TiUmyXtxcfTaY%nd&O(ZBk`8YDXOE&6D{z_NNZTgItXggGG80C2REKbZ~TX*IimuH5`)Qu2UBvV&8!SX;t+8di^z>yQ#u!8^<%Orwy zzyua*Ze#ZM`S#ZUc-&B!!4y41Y}#-@)dgTD3f1-NwT#K z9Y+dqqbi6la_*=CF(3q32YdfeA{;I8;6yw~S`wWaa{xyZ3p9~qW#RtelLoZwG9Ei$ zYnIDe>Bz?k=|VD1do~i;IEj4=8DJ^(`B%sWL=c6@zQrz6B@fFSqvuzX0Sq1ixx9DT zps=?f)-VGkq?)En$&p&;b(O63K65?#d=U@}B)rUVKcq8ykAGVtRDUWYdwI7=W0eC2 z8^Yim7s|AY$x|K(JHP6|AaIFFV6IyXJYd1nxNhTA#VoVYLc?#(wPeYn@n%?dx)moy z*y|b(9bL$PDTC(a5anftXcQ1>Wp}ohPl1}V55z`hb(n$EriqP$n0TfBrFkuf!d@>mfYkoFU;fL>L)l^FZfdX`h?^CB4Z`wRDnamNXDTckf*IOC zz6i7X^B_j1^14HhtWY^OTOjucFTQ_x%I|-9%G%5iPf39^9xxVV&8Lg#g}Ecw zO^Y~yAI6X1f0!TgdozB$;{CBHBV0c=<^Ju*rW6Ya=l*L`BskCz>eUP3Q&Il4DQiNx zK}Qx0ms^)*i=<)(UpahImFP1Q5$#JcR zYRg%SAW&M?#={lo{7(VD|6wVPc4egm|6wU9@joo37w3niJocj%@jbg=8~+ze3Ec|5 ztgy3RPw9!w`JXJsLg|O4s8SuH*>-OqVYg3ikLX^IUE8{|l8-US(x3QmhNrWMG)MG{ zufnG_IE+^aL>eHCQH?D{KgL@bxNGlVvczKF`VFZFW{WXC~-PW)77xAPKBM5RtF#kCF>zgruOP> zTdGe-K;HUPTMXZ>YDOSWb83jkqm82{6Lw1vLPUgdI0m`~iM9#KCrApv6)TLltLwu_ z?5DT$c%El-bweXqz9gAaRB+3@&C86iqAAE)pw+J-v<$^JO0@Ui=tRpHjPvJz`d@Ss zo^-dxfXdN@yBj0iOWCD4D#z59elwPs#>u}IBPc}d|FWG9+nNMk)lM5PH6RbYV}L_s z*=N7s1kg{;UuP>({Gm~bVDlA@MkWLQF_e!|F{aQKnTznqmd)aLJqRkqqIvX>IZ1J~ z7eAVW7$N^3=7dO&S#uQQSDF|IbZR`%9@xjz_r_vdQh(HJ~GgU{oDS$cv1 zTQ2XDB&mMvg)VM8FvfN!V*iD$sYaV{6lk0Q0u4)(+?`U9wcw{C#Fv_HL{i~gU5|i8rx|$P7j@G+@ zUt(m{c2=*gs#WU5{hjv>FI+PpdUGP#Y^7E5pEOSu5?fgI!IBM{594 zNu3U%xb-@s^b#4OIffe5mxf` zi6jY3*XlraRlu^T8{v=P4qH85YX4W`T(NQNjcTiezPZ`?Vr9Lg@Xr-Jxh*5$klsMB zmrojO$P+=7Cb>4G4$d0~5|)m4yG5oU0N@)iVb&|&*ey)`oZMK`x)RWzFil)^b~*^>IqS)Jf_)bNepcmB9oeVLMvdO-y+2zmF_=iK z^^mOns#!zk;IAK0)GyoN%nmlTfOjEkXk2Mody2BkL*iR7);PgbGuE>(Yv z=fvDNUk&zY{RbV%w+U{=RH`9&w^j%nr!h~}b~iSZzj8!ZQ7B^?C)NC zWvczr_GO|mj-1((1Q}vyg%z3F6c;)6Er>MOm?*)YxYlM??dH?A_*(mge=S<5zZW2q zVQeTs2xu&xQ-N|+KBjz_FsuxklSqscQMb~QlR;rKfUycZMt0GKBBS!>iwr){;hY1{ zf~-h9K5t9saXOZ;{U*_P?Mol_xA|ztD)69hYV8k48+*=X+ znU!3|3sSc{_t^kh^L*~Y-X>o2>Ha>tlI;__y*ffj11ngp@8pgk`ixB8XrY&|yR@eC9=hs)AAO zS=f?ABIdN%^06|b!jfVnNY{fPMTaIz^O0)rn$v93P0JJN-A52X7gJdR*m{eQ7(Lnj zo1$e&lsWQ@li-NYS%X&!UG*@R5O~}-mJ>^hogkvk*qttx`m`M1SdDmSd=FI4v1c<2 zcDA-GlvQdJ+h>$&8fW2K%Z&>?qAyr0R+GzCNm+8122Uznl6;nUYQ6&CGsqe875EK7 zW-x?W;j&VcUzoFeHq!59?M|F-wkTAOL6Q!N6yI6gfk=DT8jmEFhGi3qj1Pa;Sk|!L zWm0^a;-cS#1m_ZvblUOpl+1R?1;^m>RvnmdOfkPp@_3hR7!PPD$pvGVT?OYt%J(&Z z*l*JNMTnwip?e~AC1%y}krpMEcCY5Fy9CUp6ua^4XMpN?zJTe%*Nr@@-oi6wrS zi8tR#12WWEFS4Hv1>30GKx#yI+O>hV3i)M*yI1FHvm{%u_F|SU5MPg_XAI$T8Y4e} zcZG|@Fxu)yyHi){rE$1l#oMOCu%{Z55dKOhXj?KgV8FT9fE^yp7m9&u*Kj}1YfE`@ zP#ISw6;JNUCg9Yil!{JdK2tc)cUXX?XVRiW|E(2Ui6w;%>)sWi1+pFD7TJL=t~}aI zGw$>aWPBcXq`tPif+;NIwf+9P`p+yD(%D~A(GBMj?>I7*AIU(jSOroVm35AixnG^n zCOo}OSPU^U4ZA|BS6lG`G*zwKmw`14_*{j@oIVT4w{_+nj@>N=3u;W?iRG!O*3DplPi9O%{qtagNeNFP>Qmnt z7@m=wt)sd{g0obw5`=PaJ=YubVyHMPj>-~Rkv+?)Ma%0`!EmW~pOrlJi%0dy8cP)7 z4W9;3%nQcMq#!e?usIx6b-i!$E!}6_tzYrn|-P*JCa^iG_*7nBRZkll3^y3Qt4FQvJ zJv6Tf+pA*U8r~|bLn`rsrjuL2V8Wb*j(w0JYl2ZVJ;q3+0uNV1moTxw~`u25+Fa z?%4=e9qQSLeFKP&-l1!FjEHG6nDE9Tv+wwtJjyl1fAi+}VMt9&9|h^TI>(zL((sha z-~DrlY7cgEjz7*#8-Uj8(%I3hol+tDxHz{`gE{S3PK+d%vb(V??x)>Jv6iv1ku0X6 zh4#x9u9XIxU79B4P_F!A80=R{k3LQJZNN)pB4WYEhg9+Llaxmj`|bkVsBPFq*ABOI z@H*5r4KX-Hg&oQvGNk9+pITWfgwnB7pe_>mZqb+rOlUR4Y(H#UYDwyDMm+)I@0xtN zM2QybcR!SP+9e>EO9BJB=yM`|2IR>ytu!5jX4p)OsA&0>uV@b5+ z3q+RsCa2WA&k^Wn$Pxn47hRxZQ`cj?{+4!-4$y}VP-Tg~!|$N+&`o=oR+***trXW- zpW8VdZ#A%9N38RlZFeP!T}xLRaxk)#2X;E7&=PAO4q7B<+@D3AFw^GUArW#+ysNe- z#WYI(o-}%-jMz?WF3am0Im&6{wS=z5NXbVFt*BcxH+C%1K)f*&Y}lQkRY>a@S9Vsq zLj_L73(Yi+($1<_?S>t-O76#?+|-M=gF>0(#(EBxU> z`b)y;6L?5C3Du5}CJ4j43HVukZ4>1)s+fzgP4aE~fBHRH(**O^x>Q~tNV z%V3WW0R9-;9UID@ZC+N`HWKY51R7x9dz|D|>R@?ev8BF*{EC9ZEu<)B-;my*4UZY< zih5y5WG?AYYJ9Wo&}Y5+LccVb4)@=7?Nlj_tGe*ILMs)Lx6+fhviZI>+HM%5+HhKR ziLC8%OG${e?W)>3)onI$fV!nSl8`UF|IC@+vhpJwiE#?$H1 zBkE-_zOtGG&R(kG%%uGR*6<}~_j&OOapFK9TIVbgYJLu47ej=%FA zAf8IlbITQ+hI?bDkzbqCPvOHC#s3kt{)!Lb*#U>IR`)21Mk-9JX^3Mex%C)-%Yr%9 z0&=45dSS&>b>4`$`Fj6P+7wK1`~M?tYDiG8lb9%}1Iz+0l|q3SWXZ4pXCU1|tBM!o zh^fWW{ZBD}f^BPh@aEy0f)z?o!nN!Dc*w@qLN=_^x(S z_fkzD90qDc15()CBLSBaQs`Ni+b=j}36i%c&UNc{4!$Gox@Qwo3W&GxtJo4{9(Uo# za%ttwMBQAiPo>vz_N|c&U4t!o9q>yS9K_y7Csnkf=W;Xd9rdldNscPtqs3-7Gx_bx z*A&WW$5cgkHj)b&!2jWa(*T~%>M}--R#)RfAEJ4HWNU9-nSvG~F zey*n7p=sP7CjjyGW>SMazw(+i9)HQCTts^1bRrx!j^>$gm|_8|oCy6*z|`Z|{FuEa zi$r`p?FiAwHF!-zM>Hf|*24&lJrig^q*JDQ6l-I(E6JwY6PNFIJ{rw~=nNt(zsxAC zpd^S2xlY2#7E(kq6I(x+9OXLswuFm}hdy|kw00B_CvA!f_FXp+fT35ajB_E?q)C9) zPqP-nC|#-n{sbasLNs_qE9ZAu-kJqNW&*OaeYNC?^%sK|Z=1v4n}Oi<;VcHXpnm=u zd|Ux5Dy+yQa1IikH#Q$vUAA+#d7m{N_**FSXZ@OWr@=wA0MPw^LoW9JhFr5#Kah(L za)sb*VBmXuDH=^>t2t5!@j#d<{meY_)r&J1BRA4@F?@E|Yv;g`GBftaj8%+q$ZeZ8 zkT&4k@jCc*G$J4|@?l7<0f_nQU3P*7SnFY%CH78coBBp zJ2Ba7;+#FU%Of5g9-qef+ins(o&@dk-Pk$p*CIweB+Q#EGeS{juSqbak3e`}mq$_1 zQ-?o_4x=aN&&l(861l?nJIsx!?Gicj_+Ceau15ZVXGxXqo7J(d6KFF&53RKa&iF6* zn43h`PgG4!p8JU9REH`?$(=Ev3*Qty17th5ItOA`rc3w3zD)YBoG}(y<)vlv2=h%% zT&7BU2<2qoJLoSxf_6n!tmN( zMz*7`B0F1@!1-P(5ECs`y1>Jx7Hbuq>Wi6qOaM(m@76?uOcTz9-51s&P6d<3Qsx+d zd8vwfKvU$_+rqyq-~s0Y-0k15WvaBdEmsXZB~N70nlMh+7}yBmd7m=$wWM7E=SF?e z8kUg$oL>W!y+_Lk3oG}tBM@071nvo$$-U&@E6|{Q0eOq|mQ+KO;acka4%(}5Bovhi z8pHXJoJ1Dc664a;kfM~q48}$XFD!zf6(jIw>=G(Wxq@+s4`2?u%h+8U8%5ZxPYf+; z^G1KSTBaN?yueC-;`sS4G>6qXi{)tK+m=BA$Zr{&lET^{UPgAI5r7WH$P~inB)dNy z5#w@4A2B1IuJD22Lo^J4VxD^wpf6z#hS}}$@m`X4;)h*lS*!-t-8#v(tk4K)$S|K- z7Rqp&8Rz@yiyS9VTV2hK7&8OVZ_|D*Wy`$6LjW;!<`=UAVNnSZ|MxO0QSch?@{9pt zM~lOF)Z59S>y?%~mDO6v#C~39>ppV^k&+$ZqcAhlSipN%n=TB3M1#Qtq9p4vDZd*^ zI342Akr{C2d*(X(&b7rQ>SV~(aFVvE)8Wd}97pHaDk-vltR6&7%vWX9ctf5YK!}Vb zr04rD%{FBH%4KLjvSHgUb6NmksdF35bRf$s1dQ@@>Av~lO#d9zL!(HiP_gNQ7xek3 z0@oP8p1cGo$fPoa;3DlWHK_di5Kh2qF&1y>qy=Gr1Q(RphP_gQJjt|JfzW-W$(j?D z0j0W0KhGvHCcBW3T|XR~%RATdY}|8gjCHBqKco7ZEV5FWn-1IyT@UfZ*$Cw)I}5t3 ze&yWQ6Tl04Y4ls-Wilh|DBzVciZ9Usr&!Zn@om$p`VmN^DWt4r&_pQ^-ZQ+eXfUlv zs)4kNYE@{*a;K89`K1;1+L8zbdPB{@^P9alH#h3w6dM_E6spZ}gT@DKup_itcMgb~ z#ww#ER3}TsLx93-n?`Kwete9KDdwk?N98Pbyq9E(*%ZiziIh9;wZh-40OpQ$PyibU ztwtJfjd5~rl#)%iR-#}e=*SaCn!=k;TwOuq`Qn+a3B6jWGl4GB?8dIN6@ehQDkE{x`2nZ<<- z*?zZ>R?2cLFHx3UrSY(!SYAMfU#Ktwhwd8$_ydbB2q0NuQ;Y~!@L`wNt9$S>i59&S zr%{#U6t(e9A5dHdMfgoKK91`lV7|v7Lm`&=#(`g7%7q6@Xcl(mI~z$l1efP9)MIe! zCG_B==!liGFeP)sc-}fI+F*zsNrG+Ti+fFZO|ob5UH zXYKQmWF(at%kdOA0cF5Ab9!74QEJJDBc0bM!Bq`l9jnAT2!5LbXbz*-VFy4P`EtU)}(Jzz|&z|sGNjE&Ni2DczwI$52tXE`) z4sIy0VjDAM#%CK}90^v3qEQHFChJG)0$U6om92R9HlDXjMj2hOwb!~iQ$+FRi&W;} z=UnOjf;f$aw7wcB2iuQ1tplB>Vb8vifJBH9r~Q0%L)ZnI1T4haLk~TpxAh8ZA?qtz zhaaFpGz4LhHkxE&Tr*a}XDVZqql@M-Og46JNz0Ou$RjeAH~S28?yr{@&65T%7H$BfO{i ze07Y?KibiMy!nZhBEtBImFi{rCsv9gWFI_dEQNIPIuwpC#ZUZ18`!W!%d-n}oF8M> zcuX5*U+mF%mr+1I?MuI2Yn(fX1i<4}oE``C>unuAEBKZA3fL!umi!u4bNP<<@7vF9 z?t3DF|-JhrxMkEo;N#mj9H(!Wx>Ss(YM#tj+psR zy3}KBR#_Iq+(;a^1t(N~C$py$`p#v(LkxB+bZ-z&2~`t%2047NEj5E>escaTXEqbd zE2(1Ol8@nngjR{wO3j^3JO;}%?P6fxR#0wmy#VRVaf7*7rXa9W0lggIhTqpYNddxo zn*=K2M?59*5!WMSlk`K0or^N6qCn>n_NS&l%-$2>XJX7Nqg?_c-UW)7Gwek=#|(xF zP=qp@=}(4X@qA#6vo!@~;h{g9-vm%w@m5?f%&SrO87SV&%5Kl2-cz>IKjlTT5z zioY{fi~Dfq)`#?;;xw2!J$y^~g>n)=JuskoOEj{?$L8{#o#l6G^AED9RzMF4tz@Ah zhG#rH2-@woMM?`Jli1N6B}I|sU6}?!5l6kRq-C72doe&T?ij$xsyiz zhGZwhW~i_M9FEJwbQ#)K^{Yz1FaJaG$f8AG!}!g~6595RL>n#-R^9G~{~>vFB6HxB zKO_$&T$#NvM58-wBzaBSQKR5R7fOA=d(KLfTGv!c&C6jgfCb{amkd=Y1dVfqBzTn>vfFbp^Dp7?4ZtEY7mbJrN69}TzXG)atao> zk0q0L2SaE6?RpX_R7eU&G;!A^gh|pF1*$tidN!6oa{SjQ%CBX~y^{`Bp{x|}4`JAC z7(##7HRM4u52G~^B$vKoMng6M+RXB0Wa1zXZf*nyh(%-jfXBi(;1Ho8mmw{WN*cIw z+yJi=%$my-OB3?AV|P$Zdd7^&E=)3jD)k8Gh&K9GSF{ucvlgGn7NNERax#2&JG_ET z1K7!pDN|@L9lF+*J09j0){B9riZ?a3mx~bpsDAjvfsuY!Bfm9vZ_MN3KRXF`EY4mW zG<)K0apW-zppE!dw%xD;@tQ|hx+SXW5))8dnw0F_esR;c(xpLZG zV=&y3>F>S7^)AIio52X<|d{9_%THazWpRT*GLt{_i|NI^K)x4!|CbVjO<`@4Q}G4FaN_r(wY z!&6%5)pJQ{i}_f3+n5VP@%|d;zK}nSANzfc{P^|hkABQ^Y%Dxn><_;4a=Zuo7UJCE z=j7NRpM{H!9TUDkO8qJv3_KjvulgSY+(a z4O8XXK0TORFoDdIey%b2oC#TYdPw5gnFwS*6WRB-LDXRdVhFxAz0 zs`|^-{iE{|*}mJ_zDG}MS6A>$Q&UTGYv^Zct7cPI)=%Yc=xXTse2I2eO$S)}|Yr0v2AbN1Rkv zS2XyV8)ej?99ao!<(Ga~H;G<0%7tm_1!4`*e-fb(KYtRTT81jmRE_|;w1c|BZW~I` zYK@Q+`Ms9mt_NpjT1{_|{4w0}PTpUho$53amazmbeE*Gv0^QP2$bDa7J@-fW7{ux6 z@fiL%%^*~aIa37vE~$zyQj4y=&a3G4$`rrI%m=6IPKdK%auq#^m#BuL8$sN%<|IW( z{EbA*84#FOv#5R#Y;Fq+qH+5VylcGw2i`q^)J*~pp+A@$#*oUaU28syFmO>>w>FTS z2Bjr6Q?DviEgoCLL@l2^l?k3(!=qg@R1JE#8s#}{Fl?E zMkwxx%b4s%uMz~jF5|JjRIOjeoJp2Hl&fmCAio zI=qFxWt`ASjpVF9`~nRA$J5;(gf_Q_lMI%+{&BZ7k{`Qn+>R8OD^yytYPr;yS#nMA zL483#d$QV(D^hOUj|dNM>|1m4S+j!<G|7#^Pu!P6s?q3 zA_Q(TA_9k4a{Vi5i|9V?@uB(?q>=@KP*?zT9nFy(9`Uocjuf%e7j#xJ%3ewhv1JmO z%NKcfu5o(u!<1Wdt&%SKGH*dQ`95nQd+?lN0x3J~(@;CXHO+{oHjms{{ydP$H>Hqj zb(=Q7Qe`qI=b$+<^N63!LtgU=9j4}`NQ|kg2sU%3-q8bIYjlmX8{HbzMbA(Ff2Kc` z&~`Rd+QaGnx2I$2JS&%?vJWLy^mMo6Gfg9(+@IdRdQu-brnD zXZ%b!L#90@h|ERlt^M-<;_DsT>uLkF>)5t!+qToFVPo5kZ5xek+iqiX(XT|AlUuNHVuiMi{~!1n!UesSL79=IsAcKRMX^qkj5*mYpv0SN(*D%6V7S z<6%)Xi{!MrK7~?7)bNFu(F0TR8iv#gg+z&|l!E3t(DG!+lOyH?DW@egEl0J0>OdAr z;;shl{qh{?GnLhsJG3wG1yGp8CiaTfUM%z1wt-!{$M;=p#qBPTfWZ_sXI)Mbz9r}3 z?dj}x^L23b@J7fjL!k0_1t-4cz0)P+QnkiRUL$F@xnWQErerlSYPE$%QDZ-dF~6!) z-@~xMu?;azfwnG_$?bh49t2E0Wr@e<-3rJ<&L12}+xE zAL}Ju=bsoGY8fNkCULDvORTWNuI0P&`WC9rQOlGirI`C20=byIyTvxs^L6GP1N2m9 znq&ut&Q%5KW?No1p4ZzR%p_8EmPypQJT0hC`0TCv@1#zFhcq#h@$aPW2u&N#?)C?l z6N}8&jcMoknZZ}aV|XjF(bvp=HAvA_6V8=i*FQLUBVhH)lmd()q-JF&v0!HrgNGh9 zPPmM*n(tuN)}`S9H*M3)Ay?~p6*W8ZjS|u(X?Fvfj0dDaYo^U*fzMx79ZN1JRSBB8 z<*H>E3OD0hOu7+6Ll{ta735!th{jSJL7CzEhdX2Gn+D#piX7~2O>D`z^fg9n*>_)` zxv9M;hOwOxWDczMB~pHxM#~?dI~ZsV4HQ45XUIxsnK@dCPOw$ur|_-w(v|7xK&XJP z^U-m)wa@yxUl62p>HxlL);ImCaxp4IdUd%HRP0pTT>J4&x!syQtoS13S;@hM6^AQO z9H>DiB|8Dt(d7{2B)M7o3+_;5KMvZwF%mXS@gT$g=kn+~Ut_Uq>ADZn{=UMD7q;{4&WHHOJS|0jz@{z=u&FD{25jo)B$GH#{%-0#{%q<>rT%W}MA@!b z0Gqns2n4zRYf}fL{{P$5`TW_`srUGErToXHF0ty*rVe!mu&LuPz0VD@0V@9;8fsGJ zDtL`Y2%=a9+G(1uma{Zi=)Y87OPeT_XoYKQS$Uj~qhoO2+J^#`-j`niUs7&>Lk*Qu zeAAiP0NY0TE;-Kdjrn)Z93w#bq$~q&EqOW?z5hr0bn5*_`jkf<(p-tRn*Ue&1a7B} zx<+E!P)1WKZr#vrj?9m!sI-}zuQ3w_ZIKPlC(0)%m1vvHe~vs?DT4)&J|Xfi4Yxtp zC!)FL1Efz5PUp4BZ+oJOYs&9)nLtFdClMwFIK~Y>{G%$85DHohlag}|2XvBeZ89*3 z#P)ld#mMbWtD1@k2v=XIdpQhr+&arBR3(r#MZ{)NQw#*$(c-B{l*E3-%0rB|3|yAP z$6+NrE6G8q%(M_bL*w8Wn!H-R&x?-NO^5N5dAEA~${rqs?#Fr|Ggb_=J)G2#t{~{z zeZ2iX*u^+fwVLSQ;RFKdclQ1t&L?iG2z+hG|KWV%MEGCMr`yOLb?G1^4 zu9re*{W)4}!2EAVi!~hJ=^4l998 zhHkP>B5==Y)F*0UBRF2w z8cz!zhfjdlree@^;qI$oPQBKvWHTERFD|(bc)MTUCjtM0x|L#4Cvc4N%cB6`|~e1I==BRs|H@8X#e3AS*_;TjY{ z$AQ^WAOh==2kzxS`5i&D<5>@>?@`l)qXIH9v0P@gp+-Gs`7YO5#4fF0zvRp zvyW*+b|8tW0v1@IXHU~cncWtTJ>Cg0)TkcA6CWrERmq&0!U)>G zoL{7m_wX0Rf>QF#JRU6J#6os8q?&Rpm2D zeM|?pIO5FhD6gxDKQ%Bc*ArKA=D3J^X`A=kwKgii_o zL-;iRNBES#@<;gO8^Ra{9AhsEeaXe{FLC|fg-;3N5{97A>a!kh|_MFb! zLy5!UsN?o+kDkKsOt!Ug3X%1wmLRAuV?+}KUs5I}6XMa>_JC|ktO&LNbpID89_6vK~Lb1al=9qLmgUm(v>`A)4t zw|6iMV!QQAF7n57(CTj-^~)30h%~&HS=?eM?6#3oZuGg6 zikCmSCy`}GbM>oJWP7jSV&E)(Ubcu1`K3 z59^u9(~{!*DBH`4KH8Ku)&xtnEY_^!s4bOKS@FSam!6y2Es=jj|EY@At%eF-*ay{_ zcANKH0!~A%IDhDQNhBSiI7S2=nkH5KB3RXEc;yvp5u1pj(B#@mfWFWvtp9sZi*nop zJqD0a+SGsgois*~g8MR{O6)9U;x&q+!qRWWFh|g|gaj*5Bzxr&JkAI2jpw{u2V#Bv z#{?}dVt|7oq6ppH!pnyT;zKzH2EaE{SlMD~Z@RO;(%SySS^^)YIe9?yQtkMpKn#`% z=Ay9qv%>rMyTVgFRO0$!>tGM+uU0cJhI9_P`6kn6IUXiNjEaHAa<3*%94Jz(DcU!D zMqTR{Y(6 zrSRHsz@ZiV(=x;7OG;3T#AM*PLa6q&nbAV{=~T(XZCDeP59?P-SMX+&8=C`icYNA|Lfnd*#>}*2om@$OSzPg1 zffx;&ypKM$t!jQ9;JPBCwd6g|H1aN^QF@SGYi1`^jnFMsR#he_BEaJqskt*6xWRQZ z=ZX#zq3&mj%5!KzfJ{+eb;Oxpeg56Na(Ix9P@JGDRM&XxJ>(9Z4J(mfvojyri-Qsa z)G|S=%jYDu1H^cFWcU571#WvaxS^Y6WK1MHO*23%1D}fJ3}bg zt(y~mmqpB_2goBuDMk0LY;)3S;syPOLiYLw6vPJfW+#kf?_4`tBR6t`i~zwFGl_9n z>=dT-&7Jxw36dEk&@QK+6ecZ@^_kn_Ak0P<9M1KvK}?4Rjm8m)Y}{i&-c5!K3eymdv06CUN#H2tnBmQRdY2$#rZO zL<(akZ^7n5cwfopm%`1>O<{91iC~gk7)C$|6=i9HV|g69A*=?QewfTK4;yyWT$V8* zxnvuP@XXA#a$o@n(;`-x-Qy_49MH*X`ZAKvJqHhjml3wS z?}wxC1_`bDrt~pWzBJs#eYAIFw$IXhM9__wdQr4AXFHR)Q?zS)KBCcu%zzLH`p(~8 z|4mf`6QSdNtgFdXH&@3hRsT*F)86LVw55Nn8LVp4{!q2sMERmnxtRRQ^GKve)7JBs zoKtPPS1UyRZikrcR`>=9bHLvm7H_NK4xPww(8(oB?NR4&ihVSVj!3FCh&Qn)h*5k- zFbNKHab9^wh<*_WJ<723jqMqIX8f(unSIgT2H-*SE9i5`o`<}-g9wgQGtA$vvX(1k zNyW~7Z+a5gwr??NMPNv*l#+=W)#9s1vdt`e^UN*Qi^D=AFS)h`DfbsuErf*G zys&H0#MVS81Gz~m{DqvnP(i9j-kVnn&O2eZH~7~PGb9^>Pf}lEibTf;RB{R5XBRU| z36F&$5Q%zfWx7)wW!r>U_9aH!!)*R$r@%8GBC02D)ycY&#z@prhxVnpw>GL)i=z1L z@Z4^Twot>@nI;M&O&fT|J4I&P9(OGt1*)A7jRc_fOJu$fnM5N~G09q~Zsh!U3mpZT zN1-VSCrdunSqjIfSPE?jXNoPi9z@cB!6s+u6`Q^yYQ%a$x}`0h27H@2ZV~I4-&@r( zvY`vWT82bgE#-I*iz;?~NnV_l*L`_X#FuBSjGu3Be}-M?i9g1g`C1z)3Z}pk!RUMd z-m<4$iD9$dozFV^6-*WJe3)ItZ6Q|ynVi`0m%zZyAaYB*0!fN8jEeL&gqR35wP_@> ztR5WQG?f`IVi|85%LWjy}sb<@W<&`+AcwX zNAC-oGR&Vgr0?af963^?oLvnT#oCzM4DO0)CqLHjvWH~!9=tCh%)AIWu;b%-q493+xim$Vc@6MI48$?{}aw@4^uuugBLXp*wyQTK7>5pP+>Hue!mV_NBt*n9nV?Rvr5w%L63XaA!Q6M0B8e=N3E3-)}jpRcedq%(WN$24+%B zXaE(KgX-AwBD|F3MFPqW21C|xoB?8kHJmNU6iI)rcXdx?BH!>w{78c+acOs4sRuf1 z2$lc^mgpBU8}dGc07JaOlhb${&!R_C6-f`h2u-6C>4?a=%EbQ33aDi~?VL04_?k{j0#x7!s}N@SDWVp#ym@-FJmrnp7Vj_mg(-$vwZ zu$UE))hQ47?a$>ow^4{C=6p!UIn-&ygQ9I9*p)Xv8BR8OSZ0hN9K zhqhA~7QjelMfm@v?bE<8u-hUa8b)QNt}rIz+%|DDrrFjBRnf5ZXd1a}R`Ng6J7Gxs z`7cD5fFa}2SG(mzL8gFYwWZRa`#@|#wwTqmV-0NfJaVi0Z|%pDK2X|4wjnnodmb@A z@0=LV0{CDBKh_KQ<0t{lyG!Hi**m)T#|m!m&oX@L+vborJ3*WWrnhM`BZ67qV4Va9 zk`13DA^j0XXTcpI|BOZKOHuQ0j?#nrST_oT;>3o_-%(N|fPU-M&F{Pqr0ZV(q2EHO zUG{QUr`FsKc2MO}mqR$tm3Kl{EKtRhRj0ColBE7Lh_Wof)Br3lipkQAC-kzm)!&za zCymr8j2khJD#V^oM*kmN9R5cql|*ZwV0vxrai6E#m1zMW*%Rz(bbh_8Jqcm~p0q`E z($R3KBKgZ+_daQX?5VsH*Qn|*qy;5bfvD!AiwR#5VlF7Ufe;tU*KC`k3LpXXSLU&W z$xsgY0@GyMbv&pk)oT<>)dH@0HsK;?s0Ia5hseBBdnx)r$@wEJP(NpkB-nt*R7W2j z82k(X*TL+N;gCEnT(^g^Wvg0RS{uwRdZ-~&ddWU=rL-LOhb)cXhiaHH!W2VyQrEHa z5LLXCF?dlZ?S3NH@t{lQnxF}xc6FG>i(mGjjRxUZ2FOrQX&s-a)+;2TQWyGPF`6j# zv6WeyNWip=1%oOJPB8SqRkP3%mRm~8P9^io<73XZ*IT+0gY!^+4A0Hr&_&8fP?_u` zXttmy?~;^PS&&T_W`L~o9Z~$obt3&zqlNV`lGE0pt%y*V8+#Nov5p^0fC$lVL~v;@ ztcMa4TC9SwBKw_Nd!wbpI6+ig&B&PfdQ4J?|5gA47%;z zBn!;3`p#pgkxxWjo?<>xi0>>s`z1SN^OqlW96KvtZB>zLtw(-GNlOFhrd}F})5~mZ zKQJ*2`YN*3aaFw#fsRIBufQ!FCzUjNJ*(wa!IqtgWH;ljO&U-{VwCtMPA`hrSs`i#!HMSM5i-FdPS4Dq(#?&F)5?>knE=@jCe?An^1Y2=Epr$Dh9$PG`l4z+9sbm&;o9yI+@S4%xp(pb zTVfk;?Qc~{`2D9?Kqnm_m-Mwmt)z$iJJctIxf~WK<%O(}$j6}_LHq+>GC(NK{uosZ zUYSNUYXC--XR&krU5Xjd@KTtD*1!RM;*I_9egf7FW%e{m#cbO$eg<* zqkrb8D=^ml3XZME~xMVWnhx;|T|*;|S=b~i(! zS8$;?!3KcbAmGrRyQ-}|@|}swfxhl0cyy1qlFdoqIE?Hra(Ops$OerY{?rmz)9~Ly z;dq{iw`H&vS36=io$>!Y>+|&e15X!x=%1{QJ6mL0=xxjY!>&@x`2%VLJl*>it_4}h zupO6WwCvp&xoD|dn`^J7H}F~jnT5zNNL$t}kP>5_-N8HGW`hnCT4VcYICBUFOy^hkfHkX`uVi!{@3yT{&C4^ow=GAgQYp?!L_^!e>qOypAPK0$&xLkJJPV+0b{HfU_`FnO-G&Zwoop}2 z7AjG?n13lQ{J?)u+%!<-9JJ#D^kZZ=;3-5w{~&L4AK=?DL5%JJ7jbJA(XHjC6J1HPVatRGu z&26bFoD%xNAKjTFO!pJh*r~eGQoL~;Bu-+5Um@*wp=?3YP{Kyo!%^`1gyq(L(_U|` zYJ&n}boy_l{BG&|aC7;I2gH_>d1sgnrx1=padr@)-n!GHSC<2xQBWsvdSa_!t}N^? zvl@-w*J%?tgiBw$=)+$1#Tr-fXS*YD(=FWj{uR22kRumF#Am#2s!Wx!WQ6Le6a-bvZl54YcsbyjXu^U*3*(6$wmnhr*CLdD z{*(35nMra%4J6QbWtaMrt#acp<;)_kCv$)S5T)=MksLyHb$iz14amdpb!nRcQ@vHp zQg)S5yn$G!jfMcaKF{`cutK4(uXyK@K}~JIl>dNT9~dsL=D%Q9#&4x`wJx*llI)%4 zB5AsE4FGm~CyrQjwxcN1`X>=_mG#RDB2Q$Z!x*QjWaqR6;xjlm5qD+ZTIL^TJ>{$n z4kjdKoNNyf`&%sN9j2(0%ixuXq)7{beMu927BGgy^VI7t;%X{!kENr%%QS>KSQs3N zHUO7aYZRe3%=G|Z_l-V(iqcWAIHoY?tN0z4jP9UVI+jPTg@=atkJ@i)4q+mjDTe8p zD0o&fp`hDCfKd+@MYFq`)qq9Z;qvKELi5x-=`w|<<|{q^n+D$FuBP79Tb1w2k#huO zqM_#-;+y$}x0j>Pxb9LO5O>Vqab#Fjvr=b)ampj6}_X2q7fpL>XIjnu@ zoD&Z{zfJ8?*zsPVi8;Bzn>=P;93@lcTxcmunz~{P@5!$xc%7m(xr6h6*ODPrC8#6K zYMU($i7}<$)`*y5wN3a;JMuJb(qn4fOvD+vN@(s`R2Q1gJitdqV*B+B1Cs9txFPUg zt`r?Ox_#GOmc0Kg`?#_=EMxH(TEC(~65c2GC(4up{I*9`g<6{jEkxG zzPN}EnGJw9wQX(J9O)c?6SDy`YM@BOZ2brCf?&US(D!mZHe&Cj z9b~iFwdUhKY>N?QFnQkRkIn;OP-AEKH1hKAD3gcKKWCHD+mb7e|C{!Srwh*45c66h z6>tQr-s2(8!KzTEQy`a&8kM=Gb~jW3@ZB`Jq+;67tLNL$*u#Q3M$Cd5^}~X#YC&D` z&g{-!?emG>ctTTh)u^eza}NcX%T(Xn|#~HgFtM@BT_L5x?;E0 zEP;656fi0riotQ#h0B}JSAm#FJvw{4dD(d%S~in``FKk3ag>?mEw zn6XK$DL;zPvihp~T8xSbBWW#MH9`J*I%|V!i)|s&S2fr%AF3;cg>+N7wmToXmXM+V zn_ZI0Un9_?y$oSt7NE3rWW*X(5Eau7$4BB0(|Y2TJ4m{&bclXC4-lpZ|7>v$NvG0m zRWF>KRvixNiMg!8J>2wh?i%Xl!9>e$1%Wld2pDpkQwGx@#M4-r&0!lq3g3Vm4IEz% zcl}%Sk#fnVeGdOy^;rf~eJJR2{yW{RNy1u`Qk8c+0R~ik9G^@H#cVc3XsRit{;B#L zIGE|k3Bq9O?dR>r87$AzTHDbx$9?fID<{|;zu+@DkBE`p5w3}XT})`BhVK-4WdUzI zwQ`+(g*EvPx?B9Q#ZvN6L|i+#r#hDlHC$h$(=Qfi!ZJ5r;+odez;_#&}gc!VIlHX11^smUcyI2 zH#tle*mttav`WBEp1u6%@bs)7i=hwG`k14POVK_D2=eGz|69tQ~V zj+)zQ%Xm8meEEc~ApLM~sM4qk;mOq(y*LSGIgtRhlq`#-g=$_a740XvGjX;uAo+gK zaCdu=Xj^ll>=KbHlYV(i#uwfO~_!GukjFs%m4EYl=l0*cD`+)4|7kk-|M zup7GGMwHNS!Rp&6Nh+2tKSGJOGJl#~0&zSbdT!km;{K${OD+gf^&8XIXyAIX><=0& zRJs2J8Q#@|ELKpmtQFq{a9pubiGfIgtRX9w&fa;LOl=Mt*UWxZa(2RI9%h=dwb~jY16X?cl%)ZXrcBBMq$x*# zf<7|hpv!-yDUJLoymgVKh?qWqr72f{pbzYH+yNlyQ*HWh(C4D$KZ8C;k1fB>$u_nC zhs8)}euJd|33HmN$YzYT8mkyGm3dP%dPVW){MfV$!nCY>b^*Zx@^UZ};F%|jP!6A! znW=chOr{;zvVIsow(oxlTzsMA?TX35Jpk^f?LjAI3d?LfyvED@tj=X$oJ$U(zA&GJ zyLIoHXlKXx-N~~|d#z_*ZJ3CkuZ!T+o!~`rB0Q4#8>XX%FL=R*w1<$+$*cm{q$0Tc2NF5 zv7RP#xPU*g9`onqh-69%Ge1aCK&(fa%m;Y)54aur8|!gB!VV+(2i!suB?`X50>?uw zaRq?6?g7B<(3d=L%y0k3diMSU+>VX^iS_IOz-_8Dd_t{|ux5UKU#|!{yuh#g|BUr$ zm_lG;({(=g2iNcsvhHqddjnm$dOf1Q7hgSr(W%GFWfL)7ZDYKRttzE04BkWIdJ@!G z?r<8p1O#z(1v!aLXUpwhxTiU(298}V(0&~JzIod?Ub%<5(8BtbrT5|E;F`URTRVvI ztrLRU-|nzeQ}Br!-yWW*{F~GuxIQ5ZxWS-4Cc82$v5+ zmme2+W4%1g$M{i_H=2{Cs+joe^Zvcl~@T24VKQMj{euYY2Z7d+~Y* z4GpD)BpHadLWS|OqRCIYri-D>Z^7d)_g4@Z|uq zaGiX(-+6DJivwHR^&hi~xvzudT?#VX*XozU56&jxQdt5~T$4fZ@eDYpsN4e<3K-f^ zA8$uDEdHL7ud-jTx>u)UHyp?Mr7muf?K#CRX`+}1~d+V;pW3T`J z@=tGHnTw?FSTzrevEP=*R{?Z-Hj7Yi+MSC6fjnrh5fWqUA4QC{euOq{15Sv=p6`CU6p zGS|bC?!C=puxpAy93k7?hP%%V1}afF`TF8TC^xAlVm}&7sh%q~6W&3q^&O2l{oZ}L zwUtjkzC#8x=dFR16vm7H7mh%(iN+CB9O(0)`fvk0&<4Gdn8sGnEl49}ofL7wS@mHg z!)=NmcKvSVv;(C+BBwZ=^7zSm>6OChhI*usC_XKww&41VkV*D@A^go^p_~Nd65!fY zqOfMY89ScxwQcMTM}=S*siZUR#9DMEiub&tQ?%tv$Ty8OS_N1-(s0cMugx^hupRxp zqD|M0YpMNIeuOHDw_WCjK5g(&_e*FeIkycIEKci9KZOff*1p(U`rIK3ivANMjga zkUgVlD3oAn2V*1+-8GS`6V*W<6*I&Z>;tT-E1ol;vFvg<8hxsh1?Rg#fu;UXb&!(LiBdV~X5hBDNq55G||>zsdlkHO+Ekx3$$3n~t;Ly_PW%uoKdr^y`v37DeyNL>teYBf*MZ zsFAI*r;B%8`7J5%I4-iS*^+#Z6DoVxk>+5e>imRU`xG$F#}tS;;0z6Xyt?PSvB@AB z?h<@+iu~9=_d2!Z=%v}QW1p6|$B|w&BS@;%DBecX>8WH!GYrNO)(NQ|J9d>feyK>{ ztTac`m%A;7+H`HaR)F9tSijWCWRG||(O{Q|D3OdP@IY#aQ)7kkb$&Ob4u($b&+KR9 z!y1YL{l-gZDq&NsMAb(z8LQ9Ma+#_0+xxp|knKKX2rB6|CBGkcme8X8R1wl#sUQt- ziirWi0Ok`lF`lrl5=Oedj>(KOXAOBsS+#uH$w?+nR=M`D$PZz!wxnsW&T0xoi%}qE zGV~_D2Tv=l< zn+@&JMc{=lAu~3%Tw3_44TlCK-|W>%1T|TGlq1_y&}0^|4+6a?V?%EG2L^e}b&@eZ zzlEX868S=BW2=k0TK{U6Z7IPdGKX`eiAe)zkMt3))Rl%Eger*MS*fD2&Whq#a$P3vrlJ$N~>Hb^jUQ~49|W?Xvy*Db@7-uf-Uoua8}KrbZku|J@B1h5~f;o z@w&?HU44p3y8c(~i9Ef%p1=^F6*f)_5x_Sru2a&b%U! zeX12|pF^NJMA-NHW%Vr@EohALZi0=Y%8H??iAX4dy&wMEoIZCB&)Rk%{;4l@CLnCz zu{*Qd2T(j2#I2|EL3JW8unlmwxWw5ySu`xPG|w-O;!}&Uyorg+Z8BAD>j?&jJH7`u zGhq#(;z=X|B4P~fnJDB==I_VK@(V>uU;xY04CqGGQpw_5T`@DVjn?v#^X=9`Ns>8{ z!S^<@`dAcf^7`ZVn5&u_yL>T;@OAMh2FmyW#Uw_g6G(!_EbU47BP$15BHANEKtt?V z1nclWHzq-S({WX3$bDO2r>GG`ERut&-uYuz5oLLVG*Gz)7{-AQpIVVw0(oteFGJnw zJff@Q;4pHaCd#yS`EnxZ^Wssu@)XePZ5r#e$nxIr<0k7rn1xV}MA?R@O3RoZJQZ>EJgA>R8p@e8cH9F0b zD~WBH>7v^)Ni|62C5&N>iCWFOewA2+ZzQZUJ5$_YjD;b%^)Z>w1Ab|oF|D3p5Nfur z*MHnhMrY2M2{{RD^eJ41ZNjH# zBY>C=n<&zaYPxc%tMD#^7D{*C?Ky7Zq$odU?TxNG9Usf=Ol*qpKKSE0j3r_W!ix5E zLe`Nf3eyLnJ!3JDthB?vojbmioT^Bte6b3g{h#t1G`b#4KaU4^h3E!Q}?+rr6<0f$1@#6*OB&LC}94To|dU1ZY9YD^3b! zqelzVpUcIMVp}biArE5P1%``vlcTo3Rsf>>>6z)HGBnaY=t&4`!9}A2B#EkCLtHHx z9Red|unPk^FEO<*1!HG1i-*yZ{RrT;i2v)hpo2WR+tk`==6h*t?AYgGr!6xbBur!J1yuRb55Hoj1T_B) z!H0uM*i;ajEdwUv^@INk6*;DBDF2$}j!2CKyR+b0hkV8Mnyr)o`YBde9h>U&o8Q^Z z#8G4&h?&}?$+B{0V-X;?$o`Hn6L|5*ZefcfwNg3w0XYw`Cn#mV8!Lm5RetryZV|Xg zwc*wZUZqrZ2x>L&Cc7Dj3~5J`H_D?og?JZ3?3x(;ht{sKn2p>1^l zPRyEJ$ivlZBIp9a!Mydlh+&t~Mm0O6(4Ht8Dl!(8`NkcSR2SUDJ6ZO(iM2f&FdBqq;{rNFq|D)GE4O`*Z(!OUmDr5J^rHMQG&Dik}Shh zZ`i)i^5CH^%q}3h@?@U)t?WxQTM$_~?*qo<@H4Y(>*$>=cTT%ezm5@BHp7LNFcGGa zG=xB7KMjsLZ1Z!UoI}yl)_1}Q30*@g;9~(BJpq)9A1|Xsm*ajuj*=Z7cx7exR0meA;uVS z!3+QA)+FxZ->pe2TNYqzLTL!tn#4EJvk2`DMejUNmr|6WC)oO;ou(o7gs~^+C~K3T zf?_g;M)AUypw%?`55r%5ne2&sUJ)kRYd^gW`JUgvtz&+E#ZCiIS+EAtX(&&j(4n;B zm+j@GYLR*?In3^1&Zv`FDkP9Rn__~L2eMa+Dk!D9gI$t=2{IlT>B%}9N;66cHqQMj{PK)hkijtKH*6c6K%dobOrNDdNw~0Ro~!0`cOF=XnIE zK({XlY^L4#q{Qw{C2&D9Dy&rN^&#*{8AT=)1a|W90i!^>C^W6%Nh4I8@gWs0^e`xP z1h`~~rhO=*rOv>`X(57nCX1o^c73Yc-rp$}s}QU#Yj|qzODu(B~Ud zF8p(2Kj*L8!WL3L#W!$)FMUW+Cj!p`T1doR$x?@cCuuJE1#=sTJZN4bV&t`vp~xC< zmcryzze`5o!BP`FAf}e&w?;c_)h8*ZWx{Y0DXU6j>qV8n80JPw7_Nz1$VFrzQ^Yk8 zM<2jBaPg8K$Kl53y=nyq^4(O=n8p^EGKTIHh?|A8p3a@?03%sOjz<*!!4Kc0-hN{mGLz1dt$ zzxVND?_Ky4qefmW_G9y26eR8r*>P(&ShN%|oznj@?AbRgND2-MEL>|b%EbgYDx}~8 zcnU)GM~~Igrz)7*qvIE>Q(|SLT zVo%)Ugb^TwhUZ7@H$QWVc!|H3zcaa!=)8DBP%mfu1VfBixfgg#%e6YjPSWf_^Lh;1 zrPrPmrC51ei?b7w{>fy>jb9_?0qi3(P2x5DP#=xT`2HraDaW7Xj8k=A60?k$lBF9# z&<|vsY)=Lq!K6|r>vY%RCR7CH9ky`ua-UW;posb7c5ZvG*T?tw_Y*&9?J;mUHLG~i z)Ez(UpJBP_%L0Wwj7sh$1bMtz3!4}X$dGs8;b!8zcElaUW@P=gDD25H0XZyc&;n7! zpH>=hPRmGiA;ba0wZ^8wMB7DV%p7othkLquK5rybJZsf03S*ggRL$-c@Bc1mDDjH< zJVC%s0n3>kF;VDU(l1HZerYkSid#fF-+N_mVJ2~e|14($j%P2z-AH+CpcG*N%NY|! z2rKIfi=bYip@)5Ts-jh^AytknWesE6NFIEB;yj$B*z#? zfTUs)lFD+?;#j-*;~z;y5@_&Ye#&%I3Q)&_g{5$cjvDz-(0DioontHm5ydzHQ0!y~ z^^ZHbd7Avxek85F9Jnj4+og6X9V`WWG~7*+iJ=1qt?K9(JfIRzT`0j}9$m zF_^6Br{{zh=4b@S2GRJ$_!?OdZmJt~tx(<4kLAP!pf)s*mMK!HCMNJZBCkDxJ0-VB zUeAM%43t!KoF%yi5J9mgthfMvb~Y$uzSB?z*pyC+0bm{}um*R&p9WI830PsG z0uT;r;k}Pp<4x!oJ2e_W5u^d44=)EkjCd6PiX$rWv`e|P71nVB3U@tqpDEulAyf&x zd|$P>pc7AFMfA*H<1N9CWY8LeZ;$A_9!*C@y_~8IAvCsg#>FERhqL@eNroj|cTJGw zvb2|amMks}q&+g~4=oFl?*1fK{>;h=U0QN>0Rh= z<5!~0GcVZEC%xikH+9}L&(X_QI3JRrns&%cvqf<4ObDczcV;JG9mHE9;$*We=!>LS zHyQF^KdAjrcpn>Kg430XO?h6@H(!HL9WT2_4 zdenwg`oUI-vI|{k#rTabxBD`pZP$;J*qG~o8QF2e7OE-vu#Qm0v$)->e9a*0yzz;; z7)2D__Y`JR$OylwWm-M(OThk~%n9_`3db1bZee5&`ab(p1qRaY@1RG>BzBe!_iLa& z_A3%8=Bt3Q?`U2E7tuo--40f<=}*kBGr44Fl4x~P7}uhB64Z@IO(o8r$rbX!jaz&& z*4XPalJo%Ef@FB@aEV*WA{J_g9dg}WUy?}8w0Ke#TXBW&h>Q;QUA)9lPm(y!92_e< zeK~sS5Ng>k3?;D;L+mrCrZKWRq)O14VyOH1dQ(y2$Xqy|k=t1<#z;#EdlResAFB*B z_Z8-+KMk6~;$W6<^$YzHta<$|VuC5ZKCymxJsolQafci|o%Z)@O2}=Tl#204RwO$n zDXZv=bk*C6_WX$ZZQgSBtHFopb_2C1XcW7{fS6kg{xUVMRyq`vv_*o7$NnbmDcO?~ zc^^i}KC+fI{ZbRyrGeu5+NFgwBRmxN$oT*opB4#A1?9!-ovW)pF4n=AtM9So_8l8I z@_AU(-B(R|WnP`YbhFkwmGby3xERV?gRa3O;z+PGAbT$hNLs^}bs`;&{>+tt*~p~d zH2!Hje*;K0V-1rNh2b3fo^3-hl-YIDN|?nalP&XYsl4ko2tHWqJex-7jPvisH5HvH zR<$8~#PEcK6CaGa(z$#7V_GgQO%0e_TR6q}z&cHtO*^&ABYhIvCv!+>qI<|T-n=fy zHxjtte&tHrsOfK-fTp|df#vcEH=;!_%Il!#9}tJR z`SOjM;|EGuCcwDR7#cDV%!n~6&CnT_=EWN=)o5SBL=}k)RaHr_u#2eDfXCc;$S0w* zVB@ZA(0+k$g!mlzjTSyTk6A9$rIXj!_vL=~{^)J4gE5Cu;2S1flQeIlxeuvXV{|DP z%pTNncRq8DcPH1UGk^ONpj@`;$M0RA^FEU>-0jN`{@aNnf`uG}-TELkn(o@aapOAn zTeh(E_m57<@sTZU6q@A%jIC_yg=JkS%D`tQN5a=aydIzTv6+wS>%Q$7#@A3gh0R{Q zuLuY+2*V8nyQDX9UY{nP=ARR%f&as^AfNMJo&~b`|MDyVwm27Cu2prM#D)OA^H2V0 z_?Fe~qtpKK0`%1W(|+qk2t4BcdUvHlZ={AW+%2Ooo~bH{M5|!X?%Aw0QUi}Um`i9}gv`Lu{h9uN4 zf8WxF8rhfKDxhR;o8k{vY42=2X$TI&;AwiVP*WLMk$taMqK>KVJ^PaobLsQx?SA!T z_wMHAZti~QIr;v1mgn{Dcw^{(=umNQ&GYnbPO$2;`{}OQjvGw15cd$0*=yu!gyMsx%?Un`Wpy(>Mn(g@$Bm)==au`zF<-5L+QdFgs1W>_iSrqA$b1=n z)V1#GE^1&_u`I8*w_nfuM?C_*?{~IhzujEC-94_4Uo7uFc6PZfr0l$=PU@b%1z7kC zLivDosp~0BeZ1ZcJRi*++g&O1MfPF!`$}%Iq%j7b3yv5=Od^_f!T~wG*oQG`OhQ`) zuU~g`AUQ9*N-Wy^JhERda9$3y7h|~x?vu~?B~qJ~O3-TJhJ}m0V1#zE?&%gH@r>vU z7u3KTITJ1UHC!_G69$aqn*v^>9s$)?gY*EmPADt0o#^fH=@#{x4;8G5hd1ulN7Dhp zmKca%+TM|G+y=fMlOJz(4~P1`k)a*Exh>CYd|^dPr{5S>=p(Oa$GrC#H+r$-ay0x1 ze2s)^6tWarT8@IC14LfwT&X@9WqpF|*l>_Vh=Ow5^!FBH+E%`;{?@mWyl}0{g#OMk znFCol@HBJ5V1O8N69#8AOk%&x4|cHObdG=XdA8Vvi_p)_@Y5qJc%c6m-tkw^=U=bC z4QSquo{_E`1)ow6C!1SJEg%!Y(+0NaA4IwK+YZ<4A5RcsiP#zWz8N;xJ2#HVdY(?6 zm7yz)7>XLlTP^k8VW&SeRx3x=s`u+%TK_J;wnLK?Ei^Fj_v11y^RmL-3rD>EM7*BM z66o3sqCljSkBk2qjf_$Yxg8f>Isv~-4`dojM+C3Tv3k=aO z_9Tv`1k(^^+CFxH9PL9s)iSrYs{nK5l2%Rry5l0@ZPG=kKfJ`A!SXtj&dakS2^ZeOH0|N-xr_Ie8Lyk* z4Do1!>tUXPOb*cqSkky8g=n60$4wO(kB$H&U(!xCAi|2^tDQ6G?m05UWRjq(0XOWN z>^7qpTgRErHq-J2VWC<+B!69yoxq@5Jw-6jQYC`D?WrqwmI&U(vOif#Zjq#VDy*HC zp%2zpOL8F8#eP{PsY^ZDJ} z(Ccl5mB?DfL;UvVilCcQ>67n+;%2Atry{BeFeIkemqw!0B@RS+w&P4(G)R)x~=m}*!8+Av0mkItBfZPW6!Bep|5(& zjF;auhWhBAQL{CI>yWTwi9<V@`+Ft2>6+h zuimGhM)=65PQE*Q5eMANHots;?jImS137mTZ-X)2I&P0=%_i{P?5^^eal@t}R~N4B zn?HI)PwWc>XF-uxnn2iGej4CP9vsaEZ)zxPls2V6iRMAwefm~=M_%cX)cwrQ|;ZEYvDr()si^r)dv%$=IR&7>7@t%#A3x^LywzJA&V@fbIoDoaHy4 zL=HuNp~{m!!cvOsJ$cSxezZLGH*r$xg!4gC(VNzn@V{I~u z?X}ll`_v=m2Z#6z7!8o!T#aUMmO%6%TLbVGF}mxED*iwhwU{&ibLq-etNU@5=i%7> z4{vfa?)q}y)B}=RV!lDH?k6{+>y8e%2RYPxfJH}H?%~Ku&pCAD9O_}y<(4^ADj-i{ z3d}38Mty;+eCS>=JCu+hnq4mtyEW0G%eyD5ZaPW(6zsxmv>+iYyuu0t>WYsQpBjPH z(WZB;?>mc&9^4Pq#BI>21m_T5eov(TA$jyN|Ms1%T$X64<&Dz=sP+ z6^%UC{;PfwQk;2N#w5`1tkV`w)xj;EpG%!yzl8by`C#Z27*^l>ni*|m4LwWxvI)9$ zgFP#+-T5x*cLy?5a!?M9VZslonJP)Hy&a}nM6eh-rL`M$|J~Folk$hUO$ZxCz5m@ zT6y20KBG1t4mQ{*VOwIP8*;huX`XABSZR^C4B6~4QV%DQJSK~}JH$6UN8GW#YjoON zM3`Fqh83|CuH`aZO!BrSMzaFW^CRdbr&i}eS8a-zysvKw4&54!9Em! z0nABF_XtXfC!ukx*FD1Q>So9@7B$W#Zv3UjVh2a&Ny8tqy3+`V)n$EE2S2^6NY3Io z6Rpq`G1MbPEnurntU#|Gg*1VCTp|-#<#`z=-n*-vCdV%nX@Xb^I846e-m1-9^*GeL z-!mnp>6SqHREyOiMoyN~2yO3ZQWfWy0D(y}7mX0qvl}iZ%(n;76x`3tmgxXscjtpw zhXIXS1JGaMrarHUVL9di4K3ZFS@ZTD45PajAxT+Yrx@G#%MHIrZwEhb( znADJm+T1w6ul+vV=SW&oyUsDK)kfzOP~8Rw-rdv9AjsIX3qG2qn(Uea4`vXr=K z5MO*)=aG8G<{lX2ARKh6inBz#b8oK2BI)+rndOLFh>7yvj2F9s4hK_C+e?y`y99Oh z11PAVKMpGs@R0%}eoAzoN^pGyolAH}@mn2H5z15att#O42MJ*R6*k?wdH7^rF+gVl zPm_PR3zPyrk^rD+h9b0*L%8lbWQ7hw`)Gj1V08~c9jjQvu=&yXKK}8eFaBcPD5#?r zX4e~0uP$0bW_e3?BJ3*amXlCuN8g2Dq69EygskOGm<8V6?XpPGM%WoXH@DA6grfJV z=HfN~v8g3HDlfbupa}igRAB}vSOC{d!W|29@1rhEvGycv7|rhVoQ_S2LiB{LtwL8c z@5l~c75s53I|)G>q3NAa=xp$1W)surHWF@;fk!I@^a!0$V|=&td;LbeZ<7R*|2KFT zqvBb`+@4>Kw)R^?yv~KGN4JMf&OnFM-~2t4Hu*&_3h(W-n2X~6$TBCv0YAKwT>``w z2kyY}B#poX92RmvkRUqo@(MK_{P8oG%m^iRt%`FioOxD{5A~XDxY&Qt3orUy zv!XD(B=!WfqAF9tJ1FrDP{#m2d~go1yso;T+cQy!S>4usQ4Xg;Qo-hKwcGL4-<$tI z+!iOdGs#)wwnq6DkpAIaAp^gyHYeUBQJuJm+N@bu!(gbJNzN8<4DT5FFjy!9OS zHa-?>*m%^=^7WwmI(`7+ zl*wXs`3J)1rE~0#vd?!Lx$0}R^;^w>_3N7E*Zf=1FBQ1sV6SS=^}G3Qs#m}zjHwYw zXR`zH?&nc&6ZhrTu4@*@z)C39Z87@6ecyYaVzRmYYQ>}tK3L3-f2HeYVFPp2=T)L%kisMl23iZR;Oft zL-3wFd?U=RnLaI}mlnv~S9@ql`7lRMteu71rXkgmJ-wNWL8#5M2i|)MuKI{M20pHo z@rBpK7R8*8#{q!_17KRSJ6k48Y@<2JQ7uCKm z)g!#92G9lD?G3ff%kq$#^m1kl=nz5B5i->WACIo(7xJaW829MWJIL%hLr;HK^?`d2 z*oTVGT>>UZ>H0@i=`urH&@#0kt}4uEpfH_7^lykV!4h5LOwWRS`S1l+WSt(^V&XyE zcMm!DHf|i*s<&VN^hGxpk)*c1Mu>Qj%-xpwe>fuLEuV&hJwEZ<5h_pzr?^;MAIi%H zP9KZ|f|~z9neL5iFzqm=0Gu6h-9heIC91zyh8o#k#ixJ`J6J288;uWDUTg?oEtnLhqgv3=`lHtTqEtSl&)6Bf%7L?A(t z0AtSHe3p4>95D^D4pNi+{4XvkmW=?2y=fPqXI8LW+G>-S)Du7#(?W1>?~14Q-qaUf z0tvgITLjb80^-8mg62pcCXB)H4VBc^cC_ zv6rB>`2EQSU_1fv!7Dm6UGByS7)6YpRAQUqN3hx<-nffE{__oxK|BI1Yf9bIodfvW zfF3=~7}I*l3rR0RvoJi}-1gR|xew1jhIMU(>bl^w@z)?;MYA^PO0B;Ma=e35-2jkk z;McuG{DbHA>g|hFhW~Jv5!aj}z3uC^P~&y0&R+`Q0+^XL0Qmn~N1DA{{iEu@ zI_beBalpeazVM)^jrsM=;I ze}j1fb#qoS?aCcb_**~2Z*xd*4+X!0U;LdP{1Jl>$n5IS-!e@ai8rK@vl_03SnO0ZGvZo?}L>4qrDAF10NJH70D9bfUF z(9V$8+`jNHYc+WSragqpBg*Pj-!)H98Jf%b5qgN-xmOvY6s&f41_P&vnEgu&pCQ=? zv?!H-P;#-CzsHFymDdCl)v_3>WwzRE^F>A!-^PhMWH{Kj46DI%KfT9)RU4ckr z1B8HvuEP)K?EpcIzx0#%f(|J3cpgFdF>3-hJU0 zU1N}c@z&Zc2O@CzcEncr&-csiLcC{u)aimbL~~8dz+o61 z{H10YxY&F!4i?mOm<7IF%BBkc78!>#1S-7ArTV)TicO4z_Y2^wivMU>Qp6f(GQJ&# zwhcy~B+U?S^^0o-&L4Vp4>u-fKV35&6vp(?p_whJ-9lpUoZc{Ts-$r*fPDeOfFc|~ z!aCxPKRsxfJ!=6FnfV1I@B#||DY6y--@uE-V4Xvh{#xT#Cu)!X7`NYvWjhcz(?o?& zRyz*nzX5IhM2e!sCg4~h-~o?v-8+6o3+$Nfe7mN51&5KJ1OK(Uj~+ar1uh9OaqD{?q zyhJFATVIECx9TlG!UWtgU%(A+E0U8W`U$Nzwg`O-ny?Lr`onP%2lzJ`I7kT*TT{`^ z&|Enh;If}rOc*5>gB+%pr2A3x8n0)9X{9hFdDr?5^JfPf#ab@hDdPm%#;?NR!E4F7 z!2amA*kl@`cYdwwW?Mzn*{Y7RgguR+l9gT8SaakCE;}+?E))3xg*Qbjw_uvV5G!H^ z<%VzarhdL?i(&g|h!h=$FG);X0{F*wrvJL9AlER*9?r37*Dr5PyLBrQn5FMDE$ z532qdM*LPt^wefwaQbGp3uwy)!W+nb0gKN@w}3UVv3IMk5~bHuk*B5uE{(U)gp1=l zy?^aU13f+0XOu;BFBTLf6#rSiFifS@coO<|p??lM#p+_D$c;v#wG1>eKz7~ zeWTY!7*1f|1aTlZ*w63yAnk>0J+m1n z5cfMBuR6jF2=J_(ZJDu+GF*Ka@!Y{oSrpEK?`-F*D>Ybv!(}LPs~f^xkp^Vd0ukf4 z>-Xab@@l5P2MWsKBM$9$yb3(&;fSZV^%H@Bpb&&){NrP#*uSz#<5YE0S^&)0dY**( zoZ}oZ=M}86mNS9GLsS^ijecDEH}$c1P~01d6=2E4o*r(yp-f}pGEL?hRbq)mJpovp zKL^JF4u!e!#2YZy8Q`NWZ-%Y^h{24{j$>(okoo5Ov6&#vQH-te;Sen$$<2Y(k6NCf z(cKMTc>*9ff|sPeM!%tBc%F9fWt}1E;I9%4fL$XlC7QJV>7WmdhDAwSBJ&IErw$Brdw) zdgp))aziPAkT2AGAiQ9eImTNuD@sE7ag6|tzHMTV6&>ric(J;lcdcr+ykak;8G|hK z^R^HM_QTR{9hK5PmZu&kya77aO@o zYe~2e%(+-KyEA=*v=t@G#9yh?gQ}29t?=#?aZOV2pKgq9IDnlk)$VH6^2$EMBnWw* zl+I`0rRJ6arUZ^2jTp&vhomc&u8O0}kQNE7$nv12k&`EYHu z$Ay%h(oQlT2~cr-A|PH&Rn|{uL*RsHMjeaFZoywtp81EpGZlJ`bpVKn3iB@fB|Q26 zoZHxJCg5S0s;GH~8)EfXuX&$L>8!aL{YX-#JsL4{83vi)o5^>D7AdfcY`F# zz6?Pse)TdZrFTWjJMR#+eGAd3AUq^@s(@tJw!#~i9e(b2SimB;aWwGK*J%(4z4))D z8gtv)Lf)eE6RC9hZ%7DA>UnRB)31sJ&!~mVIpkWaz5JRCW}0~HY9FReLzu=nWX126 z7UMhH9MYQ%dc6(Kq)4PyoJB0PEWby7H+*T+o~V6)I64IDo!`CAnIAwjMW0W|%rRee z<_8~EowHiwgWd2KEU95aQG;bT*XK3S{1dL(2wd$KfWVqOMO8WrfsfdkHRO z`vU9f)=Fq&-)!Ru249#y)v+Op>?DF7pME?sAs~5OY9egsro6O8NR^kh7`tdJ>=RLB zvgFfGxs^xOvu3d5*4yc!AmY&YWW#@gQnrC!qVq?ZxlFAveIkKc;XslCx%?FzHjs$+ z>1MVm@H}xmZQd_KT=e;iAWyTmH+#SOuS;N){jB-FvxD2OfUL0UjFyV|no;A97v5fe zaByd~>Bj?D-@-ghs4OT8SUqj1k!$tyFgw^h#91~9Vkqvu-li+bSqW;tI+J2eajA(= z^_aM`A@YfDj4zBY%&N$kQ)+W^8*^^2O5C`9)o~u}VSZ%Ye+}zxwd>R$HZHm@fC@>S zYBT_CpcBL*SR^zqEZuX8HHy`W)w|mnO!LkC2iF8g$8!q(_)bukIu5+f#)Vha@d>z1 zah^uFG#Q~CV-_tMVO08+9`Iqu)wD&$rE%FSuW?k@wun|+0L3t)_JD+#Kf?Njuuvjlfx&_)GdJ@)eSg{lB1Sv+Ijjnpym4aJhVJCNC zr@eqNnG%y6liU_!^LuAQU&Nytl0kXWnmMh(kAtg0ApV>}~s_}Wl- z2hCvqGk;5(ajqwEhM%q!vxwxO zmeFs=oIwPTAf>)l?0u#krk$?E}Il}~C%8R4HS z*1Ym%8@&>3kWI$%mxrP}GwymjFS}dx6Nz$ijak3WeJ6tYDQN$=SR%h(;XW3UzE!+Y zyt!_*&p30`z@lA_6yhMVqQb0_<I+a?-Tc2@|q0)x-$Q(;v?I zgB#zsG4sT`fo0{|i)r_r9iPlYkTe2UOD+)1$cut$x)FE47!jBW@Lnb*@@UPb~@O+lN*9Kr6B5)1)s|l3r=P(hNcd{KlCL zRiLC^2>0iYDZ`uj1Ue&8xI4&;l0Al}R(n#Tq#)$%;U$+$i{LY?nCZT*ORr6G7#-*B z)KG`HgMx*Eg`NU*6LMVS8*<&`uW0J8nd2)l-zp_7BoH4_-O_@i*&+De`CTp~88Oih=0R}gouQQ>I8UE z%+2s0WpA@t#jDsq${yphOh20pKBn$&2KMVb4#-P|EI2Y;2r6Mfy|tGgYcQ!WsZCu1D!o!(n2^m>`4FCe@L0@AvuHdcg9oj2!_^@#we2-*^mkq<$Dckbe%U$G;1=bX zN0S1{0?Cb{H=ByB_{aB_2b@2NVxHz-vKOEFzSIE4Y->d38*a;IJ>E<(v}Fey;1rtR z?+8g>D_$vHAqjCLmiO=|0{_ubIIo=CXnBYzgR*EiT(p<6Qeok6eeq#K#m|sr(V-9*5`8{c0GO5 zuliFP{FNjyUiF7|00(u}3LC93ZAq_|pkJH)t@h|9#LG!LUN&Aft0tp4rq!BoOf_#t z4tB?^Lc{nHv1hoU?dtMPPUer7npN?d5Os+9>}1V3VGH9~0Nhfwb5;(D@6_=`gNlH< zp_37VYR-(SzSBEJ+p$2K8Sz16OV;-$?E7F>Ho`k+wdEL^$Nm;A*mF`NuJs@05H{zR z9)ojI=R<#?gT(rC(_+ zX#5t`XcKu`P%TDr!NIZz6_V@DP$+NGU?*ZH zDo5}MyUAJu*C64;%`Ajxbh=Mmm(#Am-11$M*IfC$1kZHwq`k4#d+qnyzMy;|4`#TgoHP5C4^7XXZXF~kBPr9SxsS!UwFcRGS12>)j%hA{XX&Wkqu~6*Ez6@OpnsBLXP) zbz(Eww~8d;LBfM<1YS7pUXx?Vw=+tYFZnc#^NkUE5*gYS1>X~sIC1}&d)=hisMv@s zNEP~gAo$t?FQVs}?<(J1$;&9HU5gSQ54)E*Fbxkt>7kS8KzhQaDOi7(=97S5%0Wcpho!Fibx#bL!^ja zxz(?mNO%{YXMtcMjLx^--V;Rjneijz$MOPG*ikKG-!LQ3u4a+nj3Q05+e=qGoZe_i z`*I@*5vC%){`5S`uF0f#we^9Mf;s5U8x--H;vGan#|>HO z^iP?@8+dzUYt%nuCZS|O{8oz2(LLTulU5e04`*3F#W9}kc|AhL#azDg)L|;YGwu_#8cr=~KN9~sNdGH2HlmW|5J&N}^N+Gxqj7V#m?JtyuW_9vTe3-N{ zvtjOL*+MoO-GwwrQrcsk#J!u1d5{`PjV>aOh}5_Qg^wR)tUc7@?e^GkRi^x1J zzW#Yb-@MW{^#Oqd6$=usBOEK@K^AwfY0-@B{0sV-Q7{i*nUT`Fwkp@}Xw=}8pG3xg>@ z@*w0V144W~qPU-ta*nWF|5;qXB&c|Pr&(fQp3RM^Tg`u}4|K5?241{5jY#xN^sGq; z9z|z=QyQ`{`ihXMCdJo$(#^)V~$s);q$mBY`=)ZIE=IyNcctMvNbtBu( ziz=|8(LMe{1@;jBUy(tyHaP7;lfk5%)VJMP%YtkDr@fsHmy1-kI{vY%adA3(%cO|8 z#xa-|R20|d2$7mfU)m5C`H_{jGUUsr%csxDL&wnW7LUXEm&R#6otoxe*IPEe)2pCA z4-pWflxZuVD(^cj=;=BAF6%2039&cX&Z-%5vTas(VD#yCYmtYv@g!7JF*+7H6gnVz zm}vTbfhVn@m@TL-Q;?yvpW~aYS(hDSFKvZ1FiirjOlU zM{kV;Fw1)C9cz$>W)7IE74RLF?oQci@9edRROMcX5jy-`sYClV(LMjrmoxeV=*TbO^5 z#P=X(z>5JGEk5-hqAsH@dx>DNmLFEcUB3+f*vrd2)ROk+6rSG;#KvnesdhfNBqqRE zEekCQExgWnzCR<-dp?w1d4rv~B2px}r3Rk{?RiqPF{(+_4(>iK8nXEd{8L?h~XW2P9t zHqRvVgXEK|BMavljU6xTTTG_pzRP{rQGm`F%*GN4w7vS`oxZ8ZG|!&=r)=$or^cqdK2Yv_CmlZtNrtjr9k~o-2>Y_hC4)1;2+ZK@=nWkd}<*Q)PM>Z(YKiFxGQ? zD{5pxaPw3qU7zP_{HE3u@D!vWm&|GkBIVOkCZ#Syw4Hz091lv25(}HChVenaFbDCH zC`t=V54D>)j| zSdVL^ZXoGU$^gAe+~Rjcanvvz6ixR56}lpZJ}7*GsH(q8rAV)r4Xz@n-J|UN2s{GT zw_S9)g=qaM3ix18gVjHS2=N08l^^iHjklPs%Q6P2e9*TyQv&v968aMQkoF;_1D!EP zp7k_scLRmpeI}T6LokA+>hFY3^k1f6- zsAqyzf>ky`F^AfN_{PJ8U`4^SA)uU3X)K|Wzqa#+f16}5v>MW{q}!7zktrctMTJDN z!fgUobsiBJPy3)SVOnwd#2Wr*(8?>sleLGULsVr{Wn##b_PLd&VH7^sV}BYz8BQ5g zVNx5b)wO->#R5N1lwP*_UX|twafUb}yt>z!uavvZB>g%2+ROYRaVo>~2P@3bv7N*z z!QU!vkNq2PI&4H0o+~^@v^gqP@Q32}f#$Np*pf$Gz=yo!@RrcFNfDkoU8GMIV|?; diff --git a/google/cloud/dialogflowcx/__init__.py b/google/cloud/dialogflowcx/__init__.py index 656c9978..54a45abc 100644 --- a/google/cloud/dialogflowcx/__init__.py +++ b/google/cloud/dialogflowcx/__init__.py @@ -102,6 +102,7 @@ from google.cloud.dialogflowcx_v3.types.audio_config import OutputAudioConfig from google.cloud.dialogflowcx_v3.types.audio_config import SpeechWordInfo from google.cloud.dialogflowcx_v3.types.audio_config import SynthesizeSpeechConfig +from google.cloud.dialogflowcx_v3.types.audio_config import TextToSpeechSettings from google.cloud.dialogflowcx_v3.types.audio_config import VoiceSelectionParams from google.cloud.dialogflowcx_v3.types.audio_config import AudioEncoding from google.cloud.dialogflowcx_v3.types.audio_config import OutputAudioEncoding @@ -178,6 +179,7 @@ from google.cloud.dialogflowcx_v3.types.flow import UpdateFlowRequest from google.cloud.dialogflowcx_v3.types.flow import ValidateFlowRequest from google.cloud.dialogflowcx_v3.types.fulfillment import Fulfillment +from google.cloud.dialogflowcx_v3.types.gcs import GcsDestination from google.cloud.dialogflowcx_v3.types.intent import CreateIntentRequest from google.cloud.dialogflowcx_v3.types.intent import DeleteIntentRequest from google.cloud.dialogflowcx_v3.types.intent import GetIntentRequest @@ -386,6 +388,7 @@ "OutputAudioConfig", "SpeechWordInfo", "SynthesizeSpeechConfig", + "TextToSpeechSettings", "VoiceSelectionParams", "AudioEncoding", "OutputAudioEncoding", @@ -454,6 +457,7 @@ "UpdateFlowRequest", "ValidateFlowRequest", "Fulfillment", + "GcsDestination", "CreateIntentRequest", "DeleteIntentRequest", "GetIntentRequest", diff --git a/google/cloud/dialogflowcx_v3/__init__.py b/google/cloud/dialogflowcx_v3/__init__.py index a1f42c2f..ab1eb164 100644 --- a/google/cloud/dialogflowcx_v3/__init__.py +++ b/google/cloud/dialogflowcx_v3/__init__.py @@ -70,6 +70,7 @@ from .types.audio_config import OutputAudioConfig from .types.audio_config import SpeechWordInfo from .types.audio_config import SynthesizeSpeechConfig +from .types.audio_config import TextToSpeechSettings from .types.audio_config import VoiceSelectionParams from .types.audio_config import AudioEncoding from .types.audio_config import OutputAudioEncoding @@ -138,6 +139,7 @@ from .types.flow import UpdateFlowRequest from .types.flow import ValidateFlowRequest from .types.fulfillment import Fulfillment +from .types.gcs import GcsDestination from .types.intent import CreateIntentRequest from .types.intent import DeleteIntentRequest from .types.intent import GetIntentRequest @@ -347,6 +349,7 @@ "FulfillIntentRequest", "FulfillIntentResponse", "Fulfillment", + "GcsDestination", "GetAgentRequest", "GetAgentValidationResultRequest", "GetChangelogRequest", @@ -462,6 +465,7 @@ "TestResult", "TestRunDifference", "TextInput", + "TextToSpeechSettings", "TrainFlowRequest", "TransitionCoverage", "TransitionRoute", diff --git a/google/cloud/dialogflowcx_v3/gapic_metadata.json b/google/cloud/dialogflowcx_v3/gapic_metadata.json index 7053ca4a..e89078c3 100644 --- a/google/cloud/dialogflowcx_v3/gapic_metadata.json +++ b/google/cloud/dialogflowcx_v3/gapic_metadata.json @@ -106,6 +106,56 @@ ] } } + }, + "rest": { + "libraryClient": "AgentsClient", + "rpcs": { + "CreateAgent": { + "methods": [ + "create_agent" + ] + }, + "DeleteAgent": { + "methods": [ + "delete_agent" + ] + }, + "ExportAgent": { + "methods": [ + "export_agent" + ] + }, + "GetAgent": { + "methods": [ + "get_agent" + ] + }, + "GetAgentValidationResult": { + "methods": [ + "get_agent_validation_result" + ] + }, + "ListAgents": { + "methods": [ + "list_agents" + ] + }, + "RestoreAgent": { + "methods": [ + "restore_agent" + ] + }, + "UpdateAgent": { + "methods": [ + "update_agent" + ] + }, + "ValidateAgent": { + "methods": [ + "validate_agent" + ] + } + } } } }, @@ -140,6 +190,21 @@ ] } } + }, + "rest": { + "libraryClient": "ChangelogsClient", + "rpcs": { + "GetChangelog": { + "methods": [ + "get_changelog" + ] + }, + "ListChangelogs": { + "methods": [ + "list_changelogs" + ] + } + } } } }, @@ -174,6 +239,21 @@ ] } } + }, + "rest": { + "libraryClient": "DeploymentsClient", + "rpcs": { + "GetDeployment": { + "methods": [ + "get_deployment" + ] + }, + "ListDeployments": { + "methods": [ + "list_deployments" + ] + } + } } } }, @@ -238,6 +318,36 @@ ] } } + }, + "rest": { + "libraryClient": "EntityTypesClient", + "rpcs": { + "CreateEntityType": { + "methods": [ + "create_entity_type" + ] + }, + "DeleteEntityType": { + "methods": [ + "delete_entity_type" + ] + }, + "GetEntityType": { + "methods": [ + "get_entity_type" + ] + }, + "ListEntityTypes": { + "methods": [ + "list_entity_types" + ] + }, + "UpdateEntityType": { + "methods": [ + "update_entity_type" + ] + } + } } } }, @@ -342,6 +452,56 @@ ] } } + }, + "rest": { + "libraryClient": "EnvironmentsClient", + "rpcs": { + "CreateEnvironment": { + "methods": [ + "create_environment" + ] + }, + "DeleteEnvironment": { + "methods": [ + "delete_environment" + ] + }, + "DeployFlow": { + "methods": [ + "deploy_flow" + ] + }, + "GetEnvironment": { + "methods": [ + "get_environment" + ] + }, + "ListContinuousTestResults": { + "methods": [ + "list_continuous_test_results" + ] + }, + "ListEnvironments": { + "methods": [ + "list_environments" + ] + }, + "LookupEnvironmentHistory": { + "methods": [ + "lookup_environment_history" + ] + }, + "RunContinuousTest": { + "methods": [ + "run_continuous_test" + ] + }, + "UpdateEnvironment": { + "methods": [ + "update_environment" + ] + } + } } } }, @@ -426,6 +586,46 @@ ] } } + }, + "rest": { + "libraryClient": "ExperimentsClient", + "rpcs": { + "CreateExperiment": { + "methods": [ + "create_experiment" + ] + }, + "DeleteExperiment": { + "methods": [ + "delete_experiment" + ] + }, + "GetExperiment": { + "methods": [ + "get_experiment" + ] + }, + "ListExperiments": { + "methods": [ + "list_experiments" + ] + }, + "StartExperiment": { + "methods": [ + "start_experiment" + ] + }, + "StopExperiment": { + "methods": [ + "stop_experiment" + ] + }, + "UpdateExperiment": { + "methods": [ + "update_experiment" + ] + } + } } } }, @@ -540,6 +740,61 @@ ] } } + }, + "rest": { + "libraryClient": "FlowsClient", + "rpcs": { + "CreateFlow": { + "methods": [ + "create_flow" + ] + }, + "DeleteFlow": { + "methods": [ + "delete_flow" + ] + }, + "ExportFlow": { + "methods": [ + "export_flow" + ] + }, + "GetFlow": { + "methods": [ + "get_flow" + ] + }, + "GetFlowValidationResult": { + "methods": [ + "get_flow_validation_result" + ] + }, + "ImportFlow": { + "methods": [ + "import_flow" + ] + }, + "ListFlows": { + "methods": [ + "list_flows" + ] + }, + "TrainFlow": { + "methods": [ + "train_flow" + ] + }, + "UpdateFlow": { + "methods": [ + "update_flow" + ] + }, + "ValidateFlow": { + "methods": [ + "validate_flow" + ] + } + } } } }, @@ -604,13 +859,73 @@ ] } } - } - } - }, - "Pages": { - "clients": { - "grpc": { - "libraryClient": "PagesClient", + }, + "rest": { + "libraryClient": "IntentsClient", + "rpcs": { + "CreateIntent": { + "methods": [ + "create_intent" + ] + }, + "DeleteIntent": { + "methods": [ + "delete_intent" + ] + }, + "GetIntent": { + "methods": [ + "get_intent" + ] + }, + "ListIntents": { + "methods": [ + "list_intents" + ] + }, + "UpdateIntent": { + "methods": [ + "update_intent" + ] + } + } + } + } + }, + "Pages": { + "clients": { + "grpc": { + "libraryClient": "PagesClient", + "rpcs": { + "CreatePage": { + "methods": [ + "create_page" + ] + }, + "DeletePage": { + "methods": [ + "delete_page" + ] + }, + "GetPage": { + "methods": [ + "get_page" + ] + }, + "ListPages": { + "methods": [ + "list_pages" + ] + }, + "UpdatePage": { + "methods": [ + "update_page" + ] + } + } + }, + "grpc-async": { + "libraryClient": "PagesAsyncClient", "rpcs": { "CreatePage": { "methods": [ @@ -639,8 +954,8 @@ } } }, - "grpc-async": { - "libraryClient": "PagesAsyncClient", + "rest": { + "libraryClient": "PagesClient", "rpcs": { "CreatePage": { "methods": [ @@ -732,6 +1047,36 @@ ] } } + }, + "rest": { + "libraryClient": "SecuritySettingsServiceClient", + "rpcs": { + "CreateSecuritySettings": { + "methods": [ + "create_security_settings" + ] + }, + "DeleteSecuritySettings": { + "methods": [ + "delete_security_settings" + ] + }, + "GetSecuritySettings": { + "methods": [ + "get_security_settings" + ] + }, + "ListSecuritySettings": { + "methods": [ + "list_security_settings" + ] + }, + "UpdateSecuritySettings": { + "methods": [ + "update_security_settings" + ] + } + } } } }, @@ -796,6 +1141,36 @@ ] } } + }, + "rest": { + "libraryClient": "SessionEntityTypesClient", + "rpcs": { + "CreateSessionEntityType": { + "methods": [ + "create_session_entity_type" + ] + }, + "DeleteSessionEntityType": { + "methods": [ + "delete_session_entity_type" + ] + }, + "GetSessionEntityType": { + "methods": [ + "get_session_entity_type" + ] + }, + "ListSessionEntityTypes": { + "methods": [ + "list_session_entity_types" + ] + }, + "UpdateSessionEntityType": { + "methods": [ + "update_session_entity_type" + ] + } + } } } }, @@ -850,6 +1225,31 @@ ] } } + }, + "rest": { + "libraryClient": "SessionsClient", + "rpcs": { + "DetectIntent": { + "methods": [ + "detect_intent" + ] + }, + "FulfillIntent": { + "methods": [ + "fulfill_intent" + ] + }, + "MatchIntent": { + "methods": [ + "match_intent" + ] + }, + "StreamingDetectIntent": { + "methods": [ + "streaming_detect_intent" + ] + } + } } } }, @@ -984,6 +1384,71 @@ ] } } + }, + "rest": { + "libraryClient": "TestCasesClient", + "rpcs": { + "BatchDeleteTestCases": { + "methods": [ + "batch_delete_test_cases" + ] + }, + "BatchRunTestCases": { + "methods": [ + "batch_run_test_cases" + ] + }, + "CalculateCoverage": { + "methods": [ + "calculate_coverage" + ] + }, + "CreateTestCase": { + "methods": [ + "create_test_case" + ] + }, + "ExportTestCases": { + "methods": [ + "export_test_cases" + ] + }, + "GetTestCase": { + "methods": [ + "get_test_case" + ] + }, + "GetTestCaseResult": { + "methods": [ + "get_test_case_result" + ] + }, + "ImportTestCases": { + "methods": [ + "import_test_cases" + ] + }, + "ListTestCaseResults": { + "methods": [ + "list_test_case_results" + ] + }, + "ListTestCases": { + "methods": [ + "list_test_cases" + ] + }, + "RunTestCase": { + "methods": [ + "run_test_case" + ] + }, + "UpdateTestCase": { + "methods": [ + "update_test_case" + ] + } + } } } }, @@ -1048,6 +1513,36 @@ ] } } + }, + "rest": { + "libraryClient": "TransitionRouteGroupsClient", + "rpcs": { + "CreateTransitionRouteGroup": { + "methods": [ + "create_transition_route_group" + ] + }, + "DeleteTransitionRouteGroup": { + "methods": [ + "delete_transition_route_group" + ] + }, + "GetTransitionRouteGroup": { + "methods": [ + "get_transition_route_group" + ] + }, + "ListTransitionRouteGroups": { + "methods": [ + "list_transition_route_groups" + ] + }, + "UpdateTransitionRouteGroup": { + "methods": [ + "update_transition_route_group" + ] + } + } } } }, @@ -1132,6 +1627,46 @@ ] } } + }, + "rest": { + "libraryClient": "VersionsClient", + "rpcs": { + "CompareVersions": { + "methods": [ + "compare_versions" + ] + }, + "CreateVersion": { + "methods": [ + "create_version" + ] + }, + "DeleteVersion": { + "methods": [ + "delete_version" + ] + }, + "GetVersion": { + "methods": [ + "get_version" + ] + }, + "ListVersions": { + "methods": [ + "list_versions" + ] + }, + "LoadVersion": { + "methods": [ + "load_version" + ] + }, + "UpdateVersion": { + "methods": [ + "update_version" + ] + } + } } } }, @@ -1196,6 +1731,36 @@ ] } } + }, + "rest": { + "libraryClient": "WebhooksClient", + "rpcs": { + "CreateWebhook": { + "methods": [ + "create_webhook" + ] + }, + "DeleteWebhook": { + "methods": [ + "delete_webhook" + ] + }, + "GetWebhook": { + "methods": [ + "get_webhook" + ] + }, + "ListWebhooks": { + "methods": [ + "list_webhooks" + ] + }, + "UpdateWebhook": { + "methods": [ + "update_webhook" + ] + } + } } } } diff --git a/google/cloud/dialogflowcx_v3/services/agents/async_client.py b/google/cloud/dialogflowcx_v3/services/agents/async_client.py index a19dde3d..9b9d606a 100644 --- a/google/cloud/dialogflowcx_v3/services/agents/async_client.py +++ b/google/cloud/dialogflowcx_v3/services/agents/async_client.py @@ -48,6 +48,7 @@ from google.cloud.dialogflowcx_v3.types import advanced_settings from google.cloud.dialogflowcx_v3.types import agent from google.cloud.dialogflowcx_v3.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3.types import audio_config from google.cloud.dialogflowcx_v3.types import flow from google.cloud.location import locations_pb2 # type: ignore from google.longrunning import operations_pb2 diff --git a/google/cloud/dialogflowcx_v3/services/agents/client.py b/google/cloud/dialogflowcx_v3/services/agents/client.py index 9939e7d5..6c0dd7ad 100644 --- a/google/cloud/dialogflowcx_v3/services/agents/client.py +++ b/google/cloud/dialogflowcx_v3/services/agents/client.py @@ -52,6 +52,7 @@ from google.cloud.dialogflowcx_v3.types import advanced_settings from google.cloud.dialogflowcx_v3.types import agent from google.cloud.dialogflowcx_v3.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3.types import audio_config from google.cloud.dialogflowcx_v3.types import flow from google.cloud.location import locations_pb2 # type: ignore from google.longrunning import operations_pb2 @@ -61,6 +62,7 @@ from .transports.base import AgentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import AgentsGrpcTransport from .transports.grpc_asyncio import AgentsGrpcAsyncIOTransport +from .transports.rest import AgentsRestTransport class AgentsClientMeta(type): @@ -74,6 +76,7 @@ class AgentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[AgentsTransport]] _transport_registry["grpc"] = AgentsGrpcTransport _transport_registry["grpc_asyncio"] = AgentsGrpcAsyncIOTransport + _transport_registry["rest"] = AgentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/agents/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/agents/transports/__init__.py index 703a3046..a94f62c7 100644 --- a/google/cloud/dialogflowcx_v3/services/agents/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/agents/transports/__init__.py @@ -19,15 +19,20 @@ from .base import AgentsTransport from .grpc import AgentsGrpcTransport from .grpc_asyncio import AgentsGrpcAsyncIOTransport +from .rest import AgentsRestTransport +from .rest import AgentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[AgentsTransport]] _transport_registry["grpc"] = AgentsGrpcTransport _transport_registry["grpc_asyncio"] = AgentsGrpcAsyncIOTransport +_transport_registry["rest"] = AgentsRestTransport __all__ = ( "AgentsTransport", "AgentsGrpcTransport", "AgentsGrpcAsyncIOTransport", + "AgentsRestTransport", + "AgentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/agents/transports/rest.py b/google/cloud/dialogflowcx_v3/services/agents/transports/rest.py new file mode 100644 index 00000000..0bdb46c8 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/agents/transports/rest.py @@ -0,0 +1,1890 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import agent +from google.cloud.dialogflowcx_v3.types import agent as gcdc_agent +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import AgentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class AgentsRestInterceptor: + """Interceptor for Agents. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the AgentsRestTransport. + + .. code-block:: python + class MyCustomAgentsInterceptor(AgentsRestInterceptor): + def pre_create_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_export_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_agent_validation_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_agent_validation_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_agents(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_agents(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_restore_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_restore_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_agent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = AgentsRestTransport(interceptor=MyCustomAgentsInterceptor()) + client = AgentsClient(transport=transport) + + + """ + + def pre_create_agent( + self, + request: gcdc_agent.CreateAgentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_agent.CreateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_create_agent(self, response: gcdc_agent.Agent) -> gcdc_agent.Agent: + """Post-rpc interceptor for create_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_delete_agent( + self, request: agent.DeleteAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.DeleteAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def pre_export_agent( + self, request: agent.ExportAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ExportAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_export_agent( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_agent( + self, request: agent.GetAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.GetAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_agent(self, response: agent.Agent) -> agent.Agent: + """Post-rpc interceptor for get_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_agent_validation_result( + self, + request: agent.GetAgentValidationResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[agent.GetAgentValidationResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_agent_validation_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_agent_validation_result( + self, response: agent.AgentValidationResult + ) -> agent.AgentValidationResult: + """Post-rpc interceptor for get_agent_validation_result + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_agents( + self, request: agent.ListAgentsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ListAgentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_agents + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_agents( + self, response: agent.ListAgentsResponse + ) -> agent.ListAgentsResponse: + """Post-rpc interceptor for list_agents + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_restore_agent( + self, request: agent.RestoreAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.RestoreAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for restore_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_restore_agent( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for restore_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_update_agent( + self, + request: gcdc_agent.UpdateAgentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_agent.UpdateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_update_agent(self, response: gcdc_agent.Agent) -> gcdc_agent.Agent: + """Post-rpc interceptor for update_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_validate_agent( + self, request: agent.ValidateAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ValidateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_validate_agent( + self, response: agent.AgentValidationResult + ) -> agent.AgentValidationResult: + """Post-rpc interceptor for validate_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class AgentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: AgentsRestInterceptor + + +class AgentsRestTransport(AgentsTransport): + """REST backend transport for Agents. + + Service for managing [Agents][google.cloud.dialogflow.cx.v3.Agent]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[AgentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or AgentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateAgent(AgentsRestStub): + def __hash__(self): + return hash("CreateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_agent.CreateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_agent.Agent: + r"""Call the create agent method over HTTP. + + Args: + request (~.gcdc_agent.CreateAgentRequest): + The request object. The request message for + [Agents.CreateAgent][google.cloud.dialogflow.cx.v3.Agents.CreateAgent]. + + 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: + ~.gcdc_agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3.Intent], [Entity + Types][google.cloud.dialogflow.cx.v3.EntityType], + [Flows][google.cloud.dialogflow.cx.v3.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3.Webhook], and + so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*}/agents", + "body": "agent", + }, + ] + request, metadata = self._interceptor.pre_create_agent(request, metadata) + pb_request = gcdc_agent.CreateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_agent.Agent() + pb_resp = gcdc_agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_agent(resp) + return resp + + class _DeleteAgent(AgentsRestStub): + def __hash__(self): + return hash("DeleteAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.DeleteAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete agent method over HTTP. + + Args: + request (~.agent.DeleteAgentRequest): + The request object. The request message for + [Agents.DeleteAgent][google.cloud.dialogflow.cx.v3.Agents.DeleteAgent]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_agent(request, metadata) + pb_request = agent.DeleteAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _ExportAgent(AgentsRestStub): + def __hash__(self): + return hash("ExportAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ExportAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export agent method over HTTP. + + Args: + request (~.agent.ExportAgentRequest): + The request object. The request message for + [Agents.ExportAgent][google.cloud.dialogflow.cx.v3.Agents.ExportAgent]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*}:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_agent(request, metadata) + pb_request = agent.ExportAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_agent(resp) + return resp + + class _GetAgent(AgentsRestStub): + def __hash__(self): + return hash("GetAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.GetAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.Agent: + r"""Call the get agent method over HTTP. + + Args: + request (~.agent.GetAgentRequest): + The request object. The request message for + [Agents.GetAgent][google.cloud.dialogflow.cx.v3.Agents.GetAgent]. + + 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: + ~.agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3.Intent], [Entity + Types][google.cloud.dialogflow.cx.v3.EntityType], + [Flows][google.cloud.dialogflow.cx.v3.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3.Webhook], and + so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*}", + }, + ] + request, metadata = self._interceptor.pre_get_agent(request, metadata) + pb_request = agent.GetAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.Agent() + pb_resp = agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_agent(resp) + return resp + + class _GetAgentValidationResult(AgentsRestStub): + def __hash__(self): + return hash("GetAgentValidationResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.GetAgentValidationResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.AgentValidationResult: + r"""Call the get agent validation + result method over HTTP. + + Args: + request (~.agent.GetAgentValidationResultRequest): + The request object. The request message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3.Agents.GetAgentValidationResult]. + + 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: + ~.agent.AgentValidationResult: + The response message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3.Agents.GetAgentValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/validationResult}", + }, + ] + request, metadata = self._interceptor.pre_get_agent_validation_result( + request, metadata + ) + pb_request = agent.GetAgentValidationResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.AgentValidationResult() + pb_resp = agent.AgentValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_agent_validation_result(resp) + return resp + + class _ListAgents(AgentsRestStub): + def __hash__(self): + return hash("ListAgents") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ListAgentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.ListAgentsResponse: + r"""Call the list agents method over HTTP. + + Args: + request (~.agent.ListAgentsRequest): + The request object. The request message for + [Agents.ListAgents][google.cloud.dialogflow.cx.v3.Agents.ListAgents]. + + 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: + ~.agent.ListAgentsResponse: + The response message for + [Agents.ListAgents][google.cloud.dialogflow.cx.v3.Agents.ListAgents]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*}/agents", + }, + ] + request, metadata = self._interceptor.pre_list_agents(request, metadata) + pb_request = agent.ListAgentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.ListAgentsResponse() + pb_resp = agent.ListAgentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_agents(resp) + return resp + + class _RestoreAgent(AgentsRestStub): + def __hash__(self): + return hash("RestoreAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.RestoreAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the restore agent method over HTTP. + + Args: + request (~.agent.RestoreAgentRequest): + The request object. The request message for + [Agents.RestoreAgent][google.cloud.dialogflow.cx.v3.Agents.RestoreAgent]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*}:restore", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_restore_agent(request, metadata) + pb_request = agent.RestoreAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_restore_agent(resp) + return resp + + class _UpdateAgent(AgentsRestStub): + def __hash__(self): + return hash("UpdateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_agent.UpdateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_agent.Agent: + r"""Call the update agent method over HTTP. + + Args: + request (~.gcdc_agent.UpdateAgentRequest): + The request object. The request message for + [Agents.UpdateAgent][google.cloud.dialogflow.cx.v3.Agents.UpdateAgent]. + + 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: + ~.gcdc_agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3.Intent], [Entity + Types][google.cloud.dialogflow.cx.v3.EntityType], + [Flows][google.cloud.dialogflow.cx.v3.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3.Webhook], and + so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{agent.name=projects/*/locations/*/agents/*}", + "body": "agent", + }, + ] + request, metadata = self._interceptor.pre_update_agent(request, metadata) + pb_request = gcdc_agent.UpdateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_agent.Agent() + pb_resp = gcdc_agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_agent(resp) + return resp + + class _ValidateAgent(AgentsRestStub): + def __hash__(self): + return hash("ValidateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ValidateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.AgentValidationResult: + r"""Call the validate agent method over HTTP. + + Args: + request (~.agent.ValidateAgentRequest): + The request object. The request message for + [Agents.ValidateAgent][google.cloud.dialogflow.cx.v3.Agents.ValidateAgent]. + + 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: + ~.agent.AgentValidationResult: + The response message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3.Agents.GetAgentValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*}:validate", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_agent(request, metadata) + pb_request = agent.ValidateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.AgentValidationResult() + pb_resp = agent.AgentValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_agent(resp) + return resp + + @property + def create_agent( + self, + ) -> Callable[[gcdc_agent.CreateAgentRequest], gcdc_agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_agent(self) -> Callable[[agent.DeleteAgentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_agent( + self, + ) -> Callable[[agent.ExportAgentRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_agent(self) -> Callable[[agent.GetAgentRequest], agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_agent_validation_result( + self, + ) -> Callable[[agent.GetAgentValidationResultRequest], agent.AgentValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetAgentValidationResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_agents( + self, + ) -> Callable[[agent.ListAgentsRequest], agent.ListAgentsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListAgents(self._session, self._host, self._interceptor) # type: ignore + + @property + def restore_agent( + self, + ) -> Callable[[agent.RestoreAgentRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RestoreAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_agent( + self, + ) -> Callable[[gcdc_agent.UpdateAgentRequest], gcdc_agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_agent( + self, + ) -> Callable[[agent.ValidateAgentRequest], agent.AgentValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ValidateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(AgentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(AgentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(AgentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(AgentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(AgentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("AgentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/changelogs/client.py b/google/cloud/dialogflowcx_v3/services/changelogs/client.py index d6ec83dc..e3809074 100644 --- a/google/cloud/dialogflowcx_v3/services/changelogs/client.py +++ b/google/cloud/dialogflowcx_v3/services/changelogs/client.py @@ -54,6 +54,7 @@ from .transports.base import ChangelogsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ChangelogsGrpcTransport from .transports.grpc_asyncio import ChangelogsGrpcAsyncIOTransport +from .transports.rest import ChangelogsRestTransport class ChangelogsClientMeta(type): @@ -67,6 +68,7 @@ class ChangelogsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[ChangelogsTransport]] _transport_registry["grpc"] = ChangelogsGrpcTransport _transport_registry["grpc_asyncio"] = ChangelogsGrpcAsyncIOTransport + _transport_registry["rest"] = ChangelogsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/changelogs/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/changelogs/transports/__init__.py index 34aa1f62..49d7fb49 100644 --- a/google/cloud/dialogflowcx_v3/services/changelogs/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/changelogs/transports/__init__.py @@ -19,15 +19,20 @@ from .base import ChangelogsTransport from .grpc import ChangelogsGrpcTransport from .grpc_asyncio import ChangelogsGrpcAsyncIOTransport +from .rest import ChangelogsRestTransport +from .rest import ChangelogsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[ChangelogsTransport]] _transport_registry["grpc"] = ChangelogsGrpcTransport _transport_registry["grpc_asyncio"] = ChangelogsGrpcAsyncIOTransport +_transport_registry["rest"] = ChangelogsRestTransport __all__ = ( "ChangelogsTransport", "ChangelogsGrpcTransport", "ChangelogsGrpcAsyncIOTransport", + "ChangelogsRestTransport", + "ChangelogsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/changelogs/transports/rest.py b/google/cloud/dialogflowcx_v3/services/changelogs/transports/rest.py new file mode 100644 index 00000000..62006579 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/changelogs/transports/rest.py @@ -0,0 +1,895 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import changelog + +from .base import ChangelogsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class ChangelogsRestInterceptor: + """Interceptor for Changelogs. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ChangelogsRestTransport. + + .. code-block:: python + class MyCustomChangelogsInterceptor(ChangelogsRestInterceptor): + def pre_get_changelog(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_changelog(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_changelogs(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_changelogs(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ChangelogsRestTransport(interceptor=MyCustomChangelogsInterceptor()) + client = ChangelogsClient(transport=transport) + + + """ + + def pre_get_changelog( + self, + request: changelog.GetChangelogRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[changelog.GetChangelogRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_changelog + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_changelog(self, response: changelog.Changelog) -> changelog.Changelog: + """Post-rpc interceptor for get_changelog + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_changelogs( + self, + request: changelog.ListChangelogsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[changelog.ListChangelogsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_changelogs + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_changelogs( + self, response: changelog.ListChangelogsResponse + ) -> changelog.ListChangelogsResponse: + """Post-rpc interceptor for list_changelogs + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class ChangelogsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ChangelogsRestInterceptor + + +class ChangelogsRestTransport(ChangelogsTransport): + """REST backend transport for Changelogs. + + Service for managing + [Changelogs][google.cloud.dialogflow.cx.v3.Changelog]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ChangelogsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ChangelogsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GetChangelog(ChangelogsRestStub): + def __hash__(self): + return hash("GetChangelog") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: changelog.GetChangelogRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> changelog.Changelog: + r"""Call the get changelog method over HTTP. + + Args: + request (~.changelog.GetChangelogRequest): + The request object. The request message for + [Changelogs.GetChangelog][google.cloud.dialogflow.cx.v3.Changelogs.GetChangelog]. + + 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: + ~.changelog.Changelog: + Changelogs represents a change made + to a given agent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/changelogs/*}", + }, + ] + request, metadata = self._interceptor.pre_get_changelog(request, metadata) + pb_request = changelog.GetChangelogRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = changelog.Changelog() + pb_resp = changelog.Changelog.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_changelog(resp) + return resp + + class _ListChangelogs(ChangelogsRestStub): + def __hash__(self): + return hash("ListChangelogs") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: changelog.ListChangelogsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> changelog.ListChangelogsResponse: + r"""Call the list changelogs method over HTTP. + + Args: + request (~.changelog.ListChangelogsRequest): + The request object. The request message for + [Changelogs.ListChangelogs][google.cloud.dialogflow.cx.v3.Changelogs.ListChangelogs]. + + 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: + ~.changelog.ListChangelogsResponse: + The response message for + [Changelogs.ListChangelogs][google.cloud.dialogflow.cx.v3.Changelogs.ListChangelogs]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/changelogs", + }, + ] + request, metadata = self._interceptor.pre_list_changelogs(request, metadata) + pb_request = changelog.ListChangelogsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = changelog.ListChangelogsResponse() + pb_resp = changelog.ListChangelogsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_changelogs(resp) + return resp + + @property + def get_changelog( + self, + ) -> Callable[[changelog.GetChangelogRequest], changelog.Changelog]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetChangelog(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_changelogs( + self, + ) -> Callable[[changelog.ListChangelogsRequest], changelog.ListChangelogsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListChangelogs(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(ChangelogsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(ChangelogsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ChangelogsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/deployments/client.py b/google/cloud/dialogflowcx_v3/services/deployments/client.py index c2d4e9aa..2d9f78ad 100644 --- a/google/cloud/dialogflowcx_v3/services/deployments/client.py +++ b/google/cloud/dialogflowcx_v3/services/deployments/client.py @@ -54,6 +54,7 @@ from .transports.base import DeploymentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import DeploymentsGrpcTransport from .transports.grpc_asyncio import DeploymentsGrpcAsyncIOTransport +from .transports.rest import DeploymentsRestTransport class DeploymentsClientMeta(type): @@ -67,6 +68,7 @@ class DeploymentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[DeploymentsTransport]] _transport_registry["grpc"] = DeploymentsGrpcTransport _transport_registry["grpc_asyncio"] = DeploymentsGrpcAsyncIOTransport + _transport_registry["rest"] = DeploymentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/deployments/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/deployments/transports/__init__.py index f902c685..c128af3e 100644 --- a/google/cloud/dialogflowcx_v3/services/deployments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/deployments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import DeploymentsTransport from .grpc import DeploymentsGrpcTransport from .grpc_asyncio import DeploymentsGrpcAsyncIOTransport +from .rest import DeploymentsRestTransport +from .rest import DeploymentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[DeploymentsTransport]] _transport_registry["grpc"] = DeploymentsGrpcTransport _transport_registry["grpc_asyncio"] = DeploymentsGrpcAsyncIOTransport +_transport_registry["rest"] = DeploymentsRestTransport __all__ = ( "DeploymentsTransport", "DeploymentsGrpcTransport", "DeploymentsGrpcAsyncIOTransport", + "DeploymentsRestTransport", + "DeploymentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/deployments/transports/rest.py b/google/cloud/dialogflowcx_v3/services/deployments/transports/rest.py new file mode 100644 index 00000000..05d5a8dd --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/deployments/transports/rest.py @@ -0,0 +1,906 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import deployment + +from .base import DeploymentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class DeploymentsRestInterceptor: + """Interceptor for Deployments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the DeploymentsRestTransport. + + .. code-block:: python + class MyCustomDeploymentsInterceptor(DeploymentsRestInterceptor): + def pre_get_deployment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_deployment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_deployments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_deployments(self, response): + logging.log(f"Received response: {response}") + return response + + transport = DeploymentsRestTransport(interceptor=MyCustomDeploymentsInterceptor()) + client = DeploymentsClient(transport=transport) + + + """ + + def pre_get_deployment( + self, + request: deployment.GetDeploymentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[deployment.GetDeploymentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_deployment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_deployment( + self, response: deployment.Deployment + ) -> deployment.Deployment: + """Post-rpc interceptor for get_deployment + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_deployments( + self, + request: deployment.ListDeploymentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[deployment.ListDeploymentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_deployments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_deployments( + self, response: deployment.ListDeploymentsResponse + ) -> deployment.ListDeploymentsResponse: + """Post-rpc interceptor for list_deployments + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class DeploymentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: DeploymentsRestInterceptor + + +class DeploymentsRestTransport(DeploymentsTransport): + """REST backend transport for Deployments. + + Service for managing + [Deployments][google.cloud.dialogflow.cx.v3.Deployment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[DeploymentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or DeploymentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GetDeployment(DeploymentsRestStub): + def __hash__(self): + return hash("GetDeployment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: deployment.GetDeploymentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> deployment.Deployment: + r"""Call the get deployment method over HTTP. + + Args: + request (~.deployment.GetDeploymentRequest): + The request object. The request message for + [Deployments.GetDeployment][google.cloud.dialogflow.cx.v3.Deployments.GetDeployment]. + + 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: + ~.deployment.Deployment: + Represents an deployment in an + environment. A deployment happens when a + flow version configured to be active in + the environment. You can configure + running pre-deployment steps, e.g. + running validation test cases, + experiment auto-rollout, etc. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/deployments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_deployment(request, metadata) + pb_request = deployment.GetDeploymentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = deployment.Deployment() + pb_resp = deployment.Deployment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_deployment(resp) + return resp + + class _ListDeployments(DeploymentsRestStub): + def __hash__(self): + return hash("ListDeployments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: deployment.ListDeploymentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> deployment.ListDeploymentsResponse: + r"""Call the list deployments method over HTTP. + + Args: + request (~.deployment.ListDeploymentsRequest): + The request object. The request message for + [Deployments.ListDeployments][google.cloud.dialogflow.cx.v3.Deployments.ListDeployments]. + + 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: + ~.deployment.ListDeploymentsResponse: + The response message for + [Deployments.ListDeployments][google.cloud.dialogflow.cx.v3.Deployments.ListDeployments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*}/deployments", + }, + ] + request, metadata = self._interceptor.pre_list_deployments( + request, metadata + ) + pb_request = deployment.ListDeploymentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = deployment.ListDeploymentsResponse() + pb_resp = deployment.ListDeploymentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_deployments(resp) + return resp + + @property + def get_deployment( + self, + ) -> Callable[[deployment.GetDeploymentRequest], deployment.Deployment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetDeployment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_deployments( + self, + ) -> Callable[ + [deployment.ListDeploymentsRequest], deployment.ListDeploymentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListDeployments(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(DeploymentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(DeploymentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("DeploymentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/entity_types/client.py b/google/cloud/dialogflowcx_v3/services/entity_types/client.py index 432c1300..2939c9f4 100644 --- a/google/cloud/dialogflowcx_v3/services/entity_types/client.py +++ b/google/cloud/dialogflowcx_v3/services/entity_types/client.py @@ -55,6 +55,7 @@ from .transports.base import EntityTypesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import EntityTypesGrpcTransport from .transports.grpc_asyncio import EntityTypesGrpcAsyncIOTransport +from .transports.rest import EntityTypesRestTransport class EntityTypesClientMeta(type): @@ -68,6 +69,7 @@ class EntityTypesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[EntityTypesTransport]] _transport_registry["grpc"] = EntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = EntityTypesGrpcAsyncIOTransport + _transport_registry["rest"] = EntityTypesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/entity_types/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/entity_types/transports/__init__.py index 2a22d664..0220987e 100644 --- a/google/cloud/dialogflowcx_v3/services/entity_types/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/entity_types/transports/__init__.py @@ -19,15 +19,20 @@ from .base import EntityTypesTransport from .grpc import EntityTypesGrpcTransport from .grpc_asyncio import EntityTypesGrpcAsyncIOTransport +from .rest import EntityTypesRestTransport +from .rest import EntityTypesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[EntityTypesTransport]] _transport_registry["grpc"] = EntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = EntityTypesGrpcAsyncIOTransport +_transport_registry["rest"] = EntityTypesRestTransport __all__ = ( "EntityTypesTransport", "EntityTypesGrpcTransport", "EntityTypesGrpcAsyncIOTransport", + "EntityTypesRestTransport", + "EntityTypesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/entity_types/transports/rest.py b/google/cloud/dialogflowcx_v3/services/entity_types/transports/rest.py new file mode 100644 index 00000000..382553ab --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/entity_types/transports/rest.py @@ -0,0 +1,1388 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import entity_type +from google.cloud.dialogflowcx_v3.types import entity_type as gcdc_entity_type +from google.protobuf import empty_pb2 # type: ignore + +from .base import EntityTypesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class EntityTypesRestInterceptor: + """Interceptor for EntityTypes. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the EntityTypesRestTransport. + + .. code-block:: python + class MyCustomEntityTypesInterceptor(EntityTypesRestInterceptor): + def pre_create_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_entity_types(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_entity_types(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + transport = EntityTypesRestTransport(interceptor=MyCustomEntityTypesInterceptor()) + client = EntityTypesClient(transport=transport) + + + """ + + def pre_create_entity_type( + self, + request: gcdc_entity_type.CreateEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_entity_type.CreateEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_create_entity_type( + self, response: gcdc_entity_type.EntityType + ) -> gcdc_entity_type.EntityType: + """Post-rpc interceptor for create_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_delete_entity_type( + self, + request: entity_type.DeleteEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.DeleteEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def pre_get_entity_type( + self, + request: entity_type.GetEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.GetEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_entity_type( + self, response: entity_type.EntityType + ) -> entity_type.EntityType: + """Post-rpc interceptor for get_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_entity_types( + self, + request: entity_type.ListEntityTypesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.ListEntityTypesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_entity_types + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_entity_types( + self, response: entity_type.ListEntityTypesResponse + ) -> entity_type.ListEntityTypesResponse: + """Post-rpc interceptor for list_entity_types + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_update_entity_type( + self, + request: gcdc_entity_type.UpdateEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_entity_type.UpdateEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_update_entity_type( + self, response: gcdc_entity_type.EntityType + ) -> gcdc_entity_type.EntityType: + """Post-rpc interceptor for update_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class EntityTypesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: EntityTypesRestInterceptor + + +class EntityTypesRestTransport(EntityTypesTransport): + """REST backend transport for EntityTypes. + + Service for managing + [EntityTypes][google.cloud.dialogflow.cx.v3.EntityType]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[EntityTypesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or EntityTypesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("CreateEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_entity_type.CreateEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_entity_type.EntityType: + r"""Call the create entity type method over HTTP. + + Args: + request (~.gcdc_entity_type.CreateEntityTypeRequest): + The request object. The request message for + [EntityTypes.CreateEntityType][google.cloud.dialogflow.cx.v3.EntityTypes.CreateEntityType]. + + 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: + ~.gcdc_entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/entityTypes", + "body": "entity_type", + }, + ] + request, metadata = self._interceptor.pre_create_entity_type( + request, metadata + ) + pb_request = gcdc_entity_type.CreateEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_entity_type.EntityType() + pb_resp = gcdc_entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_entity_type(resp) + return resp + + class _DeleteEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("DeleteEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.DeleteEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete entity type method over HTTP. + + Args: + request (~.entity_type.DeleteEntityTypeRequest): + The request object. The request message for + [EntityTypes.DeleteEntityType][google.cloud.dialogflow.cx.v3.EntityTypes.DeleteEntityType]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_entity_type( + request, metadata + ) + pb_request = entity_type.DeleteEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("GetEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.GetEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> entity_type.EntityType: + r"""Call the get entity type method over HTTP. + + Args: + request (~.entity_type.GetEntityTypeRequest): + The request object. The request message for + [EntityTypes.GetEntityType][google.cloud.dialogflow.cx.v3.EntityTypes.GetEntityType]. + + 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: + ~.entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_get_entity_type(request, metadata) + pb_request = entity_type.GetEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = entity_type.EntityType() + pb_resp = entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_entity_type(resp) + return resp + + class _ListEntityTypes(EntityTypesRestStub): + def __hash__(self): + return hash("ListEntityTypes") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.ListEntityTypesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> entity_type.ListEntityTypesResponse: + r"""Call the list entity types method over HTTP. + + Args: + request (~.entity_type.ListEntityTypesRequest): + The request object. The request message for + [EntityTypes.ListEntityTypes][google.cloud.dialogflow.cx.v3.EntityTypes.ListEntityTypes]. + + 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: + ~.entity_type.ListEntityTypesResponse: + The response message for + [EntityTypes.ListEntityTypes][google.cloud.dialogflow.cx.v3.EntityTypes.ListEntityTypes]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/entityTypes", + }, + ] + request, metadata = self._interceptor.pre_list_entity_types( + request, metadata + ) + pb_request = entity_type.ListEntityTypesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = entity_type.ListEntityTypesResponse() + pb_resp = entity_type.ListEntityTypesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_entity_types(resp) + return resp + + class _UpdateEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("UpdateEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_entity_type.UpdateEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_entity_type.EntityType: + r"""Call the update entity type method over HTTP. + + Args: + request (~.gcdc_entity_type.UpdateEntityTypeRequest): + The request object. The request message for + [EntityTypes.UpdateEntityType][google.cloud.dialogflow.cx.v3.EntityTypes.UpdateEntityType]. + + 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: + ~.gcdc_entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{entity_type.name=projects/*/locations/*/agents/*/entityTypes/*}", + "body": "entity_type", + }, + ] + request, metadata = self._interceptor.pre_update_entity_type( + request, metadata + ) + pb_request = gcdc_entity_type.UpdateEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_entity_type.EntityType() + pb_resp = gcdc_entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_entity_type(resp) + return resp + + @property + def create_entity_type( + self, + ) -> Callable[ + [gcdc_entity_type.CreateEntityTypeRequest], gcdc_entity_type.EntityType + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_entity_type( + self, + ) -> Callable[[entity_type.DeleteEntityTypeRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_entity_type( + self, + ) -> Callable[[entity_type.GetEntityTypeRequest], entity_type.EntityType]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_entity_types( + self, + ) -> Callable[ + [entity_type.ListEntityTypesRequest], entity_type.ListEntityTypesResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListEntityTypes(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_entity_type( + self, + ) -> Callable[ + [gcdc_entity_type.UpdateEntityTypeRequest], gcdc_entity_type.EntityType + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(EntityTypesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(EntityTypesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("EntityTypesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/environments/async_client.py b/google/cloud/dialogflowcx_v3/services/environments/async_client.py index c48b284c..d507dbf2 100644 --- a/google/cloud/dialogflowcx_v3/services/environments/async_client.py +++ b/google/cloud/dialogflowcx_v3/services/environments/async_client.py @@ -517,7 +517,6 @@ async def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.CreateEnvironmentRequest( parent="parent_value", @@ -672,7 +671,6 @@ async def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.UpdateEnvironmentRequest( environment=environment, diff --git a/google/cloud/dialogflowcx_v3/services/environments/client.py b/google/cloud/dialogflowcx_v3/services/environments/client.py index 2970ac6c..ea174278 100644 --- a/google/cloud/dialogflowcx_v3/services/environments/client.py +++ b/google/cloud/dialogflowcx_v3/services/environments/client.py @@ -59,6 +59,7 @@ from .transports.base import EnvironmentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import EnvironmentsGrpcTransport from .transports.grpc_asyncio import EnvironmentsGrpcAsyncIOTransport +from .transports.rest import EnvironmentsRestTransport class EnvironmentsClientMeta(type): @@ -72,6 +73,7 @@ class EnvironmentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[EnvironmentsTransport]] _transport_registry["grpc"] = EnvironmentsGrpcTransport _transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport + _transport_registry["rest"] = EnvironmentsRestTransport def get_transport_class( cls, @@ -880,7 +882,6 @@ def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.CreateEnvironmentRequest( parent="parent_value", @@ -1035,7 +1036,6 @@ def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.UpdateEnvironmentRequest( environment=environment, diff --git a/google/cloud/dialogflowcx_v3/services/environments/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/environments/transports/__init__.py index 0ecefb61..1f36326c 100644 --- a/google/cloud/dialogflowcx_v3/services/environments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/environments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import EnvironmentsTransport from .grpc import EnvironmentsGrpcTransport from .grpc_asyncio import EnvironmentsGrpcAsyncIOTransport +from .rest import EnvironmentsRestTransport +from .rest import EnvironmentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[EnvironmentsTransport]] _transport_registry["grpc"] = EnvironmentsGrpcTransport _transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport +_transport_registry["rest"] = EnvironmentsRestTransport __all__ = ( "EnvironmentsTransport", "EnvironmentsGrpcTransport", "EnvironmentsGrpcAsyncIOTransport", + "EnvironmentsRestTransport", + "EnvironmentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/environments/transports/rest.py b/google/cloud/dialogflowcx_v3/services/environments/transports/rest.py new file mode 100644 index 00000000..30fdc92c --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/environments/transports/rest.py @@ -0,0 +1,1909 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import environment +from google.cloud.dialogflowcx_v3.types import environment as gcdc_environment +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import EnvironmentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class EnvironmentsRestInterceptor: + """Interceptor for Environments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the EnvironmentsRestTransport. + + .. code-block:: python + class MyCustomEnvironmentsInterceptor(EnvironmentsRestInterceptor): + def pre_create_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_environment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_deploy_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_deploy_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_environment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_continuous_test_results(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_continuous_test_results(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_environments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_environments(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_lookup_environment_history(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_lookup_environment_history(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_run_continuous_test(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_run_continuous_test(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_environment(self, response): + logging.log(f"Received response: {response}") + return response + + transport = EnvironmentsRestTransport(interceptor=MyCustomEnvironmentsInterceptor()) + client = EnvironmentsClient(transport=transport) + + + """ + + def pre_create_environment( + self, + request: gcdc_environment.CreateEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_environment.CreateEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_create_environment( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for create_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_delete_environment( + self, + request: environment.DeleteEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.DeleteEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def pre_deploy_flow( + self, + request: environment.DeployFlowRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.DeployFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for deploy_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_deploy_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for deploy_flow + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_environment( + self, + request: environment.GetEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.GetEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_environment( + self, response: environment.Environment + ) -> environment.Environment: + """Post-rpc interceptor for get_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_continuous_test_results( + self, + request: environment.ListContinuousTestResultsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.ListContinuousTestResultsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_continuous_test_results + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_continuous_test_results( + self, response: environment.ListContinuousTestResultsResponse + ) -> environment.ListContinuousTestResultsResponse: + """Post-rpc interceptor for list_continuous_test_results + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_environments( + self, + request: environment.ListEnvironmentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.ListEnvironmentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_environments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_environments( + self, response: environment.ListEnvironmentsResponse + ) -> environment.ListEnvironmentsResponse: + """Post-rpc interceptor for list_environments + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_lookup_environment_history( + self, + request: environment.LookupEnvironmentHistoryRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.LookupEnvironmentHistoryRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for lookup_environment_history + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_lookup_environment_history( + self, response: environment.LookupEnvironmentHistoryResponse + ) -> environment.LookupEnvironmentHistoryResponse: + """Post-rpc interceptor for lookup_environment_history + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_run_continuous_test( + self, + request: environment.RunContinuousTestRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.RunContinuousTestRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for run_continuous_test + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_run_continuous_test( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for run_continuous_test + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_update_environment( + self, + request: gcdc_environment.UpdateEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_environment.UpdateEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_update_environment( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for update_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class EnvironmentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: EnvironmentsRestInterceptor + + +class EnvironmentsRestTransport(EnvironmentsTransport): + """REST backend transport for Environments. + + Service for managing + [Environments][google.cloud.dialogflow.cx.v3.Environment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[EnvironmentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or EnvironmentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("CreateEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_environment.CreateEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the create environment method over HTTP. + + Args: + request (~.gcdc_environment.CreateEnvironmentRequest): + The request object. The request message for + [Environments.CreateEnvironment][google.cloud.dialogflow.cx.v3.Environments.CreateEnvironment]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/environments", + "body": "environment", + }, + ] + request, metadata = self._interceptor.pre_create_environment( + request, metadata + ) + pb_request = gcdc_environment.CreateEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_environment(resp) + return resp + + class _DeleteEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("DeleteEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.DeleteEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete environment method over HTTP. + + Args: + request (~.environment.DeleteEnvironmentRequest): + The request object. The request message for + [Environments.DeleteEnvironment][google.cloud.dialogflow.cx.v3.Environments.DeleteEnvironment]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_environment( + request, metadata + ) + pb_request = environment.DeleteEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _DeployFlow(EnvironmentsRestStub): + def __hash__(self): + return hash("DeployFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.DeployFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the deploy flow method over HTTP. + + Args: + request (~.environment.DeployFlowRequest): + The request object. The request message for + [Environments.DeployFlow][google.cloud.dialogflow.cx.v3.Environments.DeployFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{environment=projects/*/locations/*/agents/*/environments/*}:deployFlow", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_deploy_flow(request, metadata) + pb_request = environment.DeployFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_deploy_flow(resp) + return resp + + class _GetEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("GetEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.GetEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.Environment: + r"""Call the get environment method over HTTP. + + Args: + request (~.environment.GetEnvironmentRequest): + The request object. The request message for + [Environments.GetEnvironment][google.cloud.dialogflow.cx.v3.Environments.GetEnvironment]. + + 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: + ~.environment.Environment: + Represents an environment for an + agent. You can create multiple versions + of your agent and publish them to + separate environments. When you edit an + agent, you are editing the draft agent. + At any point, you can save the draft + agent as an agent version, which is an + immutable snapshot of your agent. When + you save the draft agent, it is + published to the default environment. + When you create agent versions, you can + publish them to custom environments. You + can create a variety of custom + environments for testing, development, + production, etc. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_environment(request, metadata) + pb_request = environment.GetEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.Environment() + pb_resp = environment.Environment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_environment(resp) + return resp + + class _ListContinuousTestResults(EnvironmentsRestStub): + def __hash__(self): + return hash("ListContinuousTestResults") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.ListContinuousTestResultsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.ListContinuousTestResultsResponse: + r"""Call the list continuous test + results method over HTTP. + + Args: + request (~.environment.ListContinuousTestResultsRequest): + The request object. The request message for + [Environments.ListContinuousTestResults][google.cloud.dialogflow.cx.v3.Environments.ListContinuousTestResults]. + + 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: + ~.environment.ListContinuousTestResultsResponse: + The response message for + [Environments.ListTestCaseResults][]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*}/continuousTestResults", + }, + ] + request, metadata = self._interceptor.pre_list_continuous_test_results( + request, metadata + ) + pb_request = environment.ListContinuousTestResultsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.ListContinuousTestResultsResponse() + pb_resp = environment.ListContinuousTestResultsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_continuous_test_results(resp) + return resp + + class _ListEnvironments(EnvironmentsRestStub): + def __hash__(self): + return hash("ListEnvironments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.ListEnvironmentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.ListEnvironmentsResponse: + r"""Call the list environments method over HTTP. + + Args: + request (~.environment.ListEnvironmentsRequest): + The request object. The request message for + [Environments.ListEnvironments][google.cloud.dialogflow.cx.v3.Environments.ListEnvironments]. + + 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: + ~.environment.ListEnvironmentsResponse: + The response message for + [Environments.ListEnvironments][google.cloud.dialogflow.cx.v3.Environments.ListEnvironments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/environments", + }, + ] + request, metadata = self._interceptor.pre_list_environments( + request, metadata + ) + pb_request = environment.ListEnvironmentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.ListEnvironmentsResponse() + pb_resp = environment.ListEnvironmentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_environments(resp) + return resp + + class _LookupEnvironmentHistory(EnvironmentsRestStub): + def __hash__(self): + return hash("LookupEnvironmentHistory") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.LookupEnvironmentHistoryRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.LookupEnvironmentHistoryResponse: + r"""Call the lookup environment + history method over HTTP. + + Args: + request (~.environment.LookupEnvironmentHistoryRequest): + The request object. The request message for + [Environments.LookupEnvironmentHistory][google.cloud.dialogflow.cx.v3.Environments.LookupEnvironmentHistory]. + + 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: + ~.environment.LookupEnvironmentHistoryResponse: + The response message for + [Environments.LookupEnvironmentHistory][google.cloud.dialogflow.cx.v3.Environments.LookupEnvironmentHistory]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*}:lookupEnvironmentHistory", + }, + ] + request, metadata = self._interceptor.pre_lookup_environment_history( + request, metadata + ) + pb_request = environment.LookupEnvironmentHistoryRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.LookupEnvironmentHistoryResponse() + pb_resp = environment.LookupEnvironmentHistoryResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_lookup_environment_history(resp) + return resp + + class _RunContinuousTest(EnvironmentsRestStub): + def __hash__(self): + return hash("RunContinuousTest") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.RunContinuousTestRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the run continuous test method over HTTP. + + Args: + request (~.environment.RunContinuousTestRequest): + The request object. The request message for + [Environments.RunContinuousTest][google.cloud.dialogflow.cx.v3.Environments.RunContinuousTest]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{environment=projects/*/locations/*/agents/*/environments/*}:runContinuousTest", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_run_continuous_test( + request, metadata + ) + pb_request = environment.RunContinuousTestRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_run_continuous_test(resp) + return resp + + class _UpdateEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("UpdateEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_environment.UpdateEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the update environment method over HTTP. + + Args: + request (~.gcdc_environment.UpdateEnvironmentRequest): + The request object. The request message for + [Environments.UpdateEnvironment][google.cloud.dialogflow.cx.v3.Environments.UpdateEnvironment]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{environment.name=projects/*/locations/*/agents/*/environments/*}", + "body": "environment", + }, + ] + request, metadata = self._interceptor.pre_update_environment( + request, metadata + ) + pb_request = gcdc_environment.UpdateEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_environment(resp) + return resp + + @property + def create_environment( + self, + ) -> Callable[ + [gcdc_environment.CreateEnvironmentRequest], operations_pb2.Operation + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_environment( + self, + ) -> Callable[[environment.DeleteEnvironmentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def deploy_flow( + self, + ) -> Callable[[environment.DeployFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeployFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_environment( + self, + ) -> Callable[[environment.GetEnvironmentRequest], environment.Environment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_continuous_test_results( + self, + ) -> Callable[ + [environment.ListContinuousTestResultsRequest], + environment.ListContinuousTestResultsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListContinuousTestResults(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_environments( + self, + ) -> Callable[ + [environment.ListEnvironmentsRequest], environment.ListEnvironmentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListEnvironments(self._session, self._host, self._interceptor) # type: ignore + + @property + def lookup_environment_history( + self, + ) -> Callable[ + [environment.LookupEnvironmentHistoryRequest], + environment.LookupEnvironmentHistoryResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._LookupEnvironmentHistory(self._session, self._host, self._interceptor) # type: ignore + + @property + def run_continuous_test( + self, + ) -> Callable[[environment.RunContinuousTestRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RunContinuousTest(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_environment( + self, + ) -> Callable[ + [gcdc_environment.UpdateEnvironmentRequest], operations_pb2.Operation + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(EnvironmentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(EnvironmentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("EnvironmentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/experiments/client.py b/google/cloud/dialogflowcx_v3/services/experiments/client.py index 6b5499ec..3e359922 100644 --- a/google/cloud/dialogflowcx_v3/services/experiments/client.py +++ b/google/cloud/dialogflowcx_v3/services/experiments/client.py @@ -57,6 +57,7 @@ from .transports.base import ExperimentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ExperimentsGrpcTransport from .transports.grpc_asyncio import ExperimentsGrpcAsyncIOTransport +from .transports.rest import ExperimentsRestTransport class ExperimentsClientMeta(type): @@ -70,6 +71,7 @@ class ExperimentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[ExperimentsTransport]] _transport_registry["grpc"] = ExperimentsGrpcTransport _transport_registry["grpc_asyncio"] = ExperimentsGrpcAsyncIOTransport + _transport_registry["rest"] = ExperimentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/experiments/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/experiments/transports/__init__.py index 3a6183c3..7aeb3d45 100644 --- a/google/cloud/dialogflowcx_v3/services/experiments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/experiments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import ExperimentsTransport from .grpc import ExperimentsGrpcTransport from .grpc_asyncio import ExperimentsGrpcAsyncIOTransport +from .rest import ExperimentsRestTransport +from .rest import ExperimentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[ExperimentsTransport]] _transport_registry["grpc"] = ExperimentsGrpcTransport _transport_registry["grpc_asyncio"] = ExperimentsGrpcAsyncIOTransport +_transport_registry["rest"] = ExperimentsRestTransport __all__ = ( "ExperimentsTransport", "ExperimentsGrpcTransport", "ExperimentsGrpcAsyncIOTransport", + "ExperimentsRestTransport", + "ExperimentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/experiments/transports/rest.py b/google/cloud/dialogflowcx_v3/services/experiments/transports/rest.py new file mode 100644 index 00000000..5c4fc9be --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/experiments/transports/rest.py @@ -0,0 +1,1569 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import experiment +from google.cloud.dialogflowcx_v3.types import experiment as gcdc_experiment +from google.protobuf import empty_pb2 # type: ignore + +from .base import ExperimentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class ExperimentsRestInterceptor: + """Interceptor for Experiments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ExperimentsRestTransport. + + .. code-block:: python + class MyCustomExperimentsInterceptor(ExperimentsRestInterceptor): + def pre_create_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_experiments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_experiments(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_start_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_start_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_stop_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_stop_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ExperimentsRestTransport(interceptor=MyCustomExperimentsInterceptor()) + client = ExperimentsClient(transport=transport) + + + """ + + def pre_create_experiment( + self, + request: gcdc_experiment.CreateExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_experiment.CreateExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_create_experiment( + self, response: gcdc_experiment.Experiment + ) -> gcdc_experiment.Experiment: + """Post-rpc interceptor for create_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_delete_experiment( + self, + request: experiment.DeleteExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.DeleteExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def pre_get_experiment( + self, + request: experiment.GetExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.GetExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for get_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_experiments( + self, + request: experiment.ListExperimentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.ListExperimentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_experiments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_experiments( + self, response: experiment.ListExperimentsResponse + ) -> experiment.ListExperimentsResponse: + """Post-rpc interceptor for list_experiments + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_start_experiment( + self, + request: experiment.StartExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.StartExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for start_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_start_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for start_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_stop_experiment( + self, + request: experiment.StopExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.StopExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for stop_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_stop_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for stop_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_update_experiment( + self, + request: gcdc_experiment.UpdateExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_experiment.UpdateExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_update_experiment( + self, response: gcdc_experiment.Experiment + ) -> gcdc_experiment.Experiment: + """Post-rpc interceptor for update_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class ExperimentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ExperimentsRestInterceptor + + +class ExperimentsRestTransport(ExperimentsTransport): + """REST backend transport for Experiments. + + Service for managing + [Experiments][google.cloud.dialogflow.cx.v3.Experiment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ExperimentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ExperimentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("CreateExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_experiment.CreateExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_experiment.Experiment: + r"""Call the create experiment method over HTTP. + + Args: + request (~.gcdc_experiment.CreateExperimentRequest): + The request object. The request message for + [Experiments.CreateExperiment][google.cloud.dialogflow.cx.v3.Experiments.CreateExperiment]. + + 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: + ~.gcdc_experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*}/experiments", + "body": "experiment", + }, + ] + request, metadata = self._interceptor.pre_create_experiment( + request, metadata + ) + pb_request = gcdc_experiment.CreateExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_experiment.Experiment() + pb_resp = gcdc_experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_experiment(resp) + return resp + + class _DeleteExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("DeleteExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.DeleteExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete experiment method over HTTP. + + Args: + request (~.experiment.DeleteExperimentRequest): + The request object. The request message for + [Experiments.DeleteExperiment][google.cloud.dialogflow.cx.v3.Experiments.DeleteExperiment]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_experiment( + request, metadata + ) + pb_request = experiment.DeleteExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("GetExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.GetExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the get experiment method over HTTP. + + Args: + request (~.experiment.GetExperimentRequest): + The request object. The request message for + [Experiments.GetExperiment][google.cloud.dialogflow.cx.v3.Experiments.GetExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_experiment(request, metadata) + pb_request = experiment.GetExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_experiment(resp) + return resp + + class _ListExperiments(ExperimentsRestStub): + def __hash__(self): + return hash("ListExperiments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.ListExperimentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.ListExperimentsResponse: + r"""Call the list experiments method over HTTP. + + Args: + request (~.experiment.ListExperimentsRequest): + The request object. The request message for + [Experiments.ListExperiments][google.cloud.dialogflow.cx.v3.Experiments.ListExperiments]. + + 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: + ~.experiment.ListExperimentsResponse: + The response message for + [Experiments.ListExperiments][google.cloud.dialogflow.cx.v3.Experiments.ListExperiments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*}/experiments", + }, + ] + request, metadata = self._interceptor.pre_list_experiments( + request, metadata + ) + pb_request = experiment.ListExperimentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.ListExperimentsResponse() + pb_resp = experiment.ListExperimentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_experiments(resp) + return resp + + class _StartExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("StartExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.StartExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the start experiment method over HTTP. + + Args: + request (~.experiment.StartExperimentRequest): + The request object. The request message for + [Experiments.StartExperiment][google.cloud.dialogflow.cx.v3.Experiments.StartExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:start", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_start_experiment( + request, metadata + ) + pb_request = experiment.StartExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_start_experiment(resp) + return resp + + class _StopExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("StopExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.StopExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the stop experiment method over HTTP. + + Args: + request (~.experiment.StopExperimentRequest): + The request object. The request message for + [Experiments.StopExperiment][google.cloud.dialogflow.cx.v3.Experiments.StopExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:stop", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_stop_experiment(request, metadata) + pb_request = experiment.StopExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_stop_experiment(resp) + return resp + + class _UpdateExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("UpdateExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_experiment.UpdateExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_experiment.Experiment: + r"""Call the update experiment method over HTTP. + + Args: + request (~.gcdc_experiment.UpdateExperimentRequest): + The request object. The request message for + [Experiments.UpdateExperiment][google.cloud.dialogflow.cx.v3.Experiments.UpdateExperiment]. + + 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: + ~.gcdc_experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{experiment.name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + "body": "experiment", + }, + ] + request, metadata = self._interceptor.pre_update_experiment( + request, metadata + ) + pb_request = gcdc_experiment.UpdateExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_experiment.Experiment() + pb_resp = gcdc_experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_experiment(resp) + return resp + + @property + def create_experiment( + self, + ) -> Callable[ + [gcdc_experiment.CreateExperimentRequest], gcdc_experiment.Experiment + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_experiment( + self, + ) -> Callable[[experiment.DeleteExperimentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_experiment( + self, + ) -> Callable[[experiment.GetExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_experiments( + self, + ) -> Callable[ + [experiment.ListExperimentsRequest], experiment.ListExperimentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListExperiments(self._session, self._host, self._interceptor) # type: ignore + + @property + def start_experiment( + self, + ) -> Callable[[experiment.StartExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StartExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def stop_experiment( + self, + ) -> Callable[[experiment.StopExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StopExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_experiment( + self, + ) -> Callable[ + [gcdc_experiment.UpdateExperimentRequest], gcdc_experiment.Experiment + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(ExperimentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(ExperimentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ExperimentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/flows/client.py b/google/cloud/dialogflowcx_v3/services/flows/client.py index 3cd59da5..9857a26b 100644 --- a/google/cloud/dialogflowcx_v3/services/flows/client.py +++ b/google/cloud/dialogflowcx_v3/services/flows/client.py @@ -62,6 +62,7 @@ from .transports.base import FlowsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import FlowsGrpcTransport from .transports.grpc_asyncio import FlowsGrpcAsyncIOTransport +from .transports.rest import FlowsRestTransport class FlowsClientMeta(type): @@ -75,6 +76,7 @@ class FlowsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[FlowsTransport]] _transport_registry["grpc"] = FlowsGrpcTransport _transport_registry["grpc_asyncio"] = FlowsGrpcAsyncIOTransport + _transport_registry["rest"] = FlowsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/flows/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/flows/transports/__init__.py index d27c76de..5a0a6683 100644 --- a/google/cloud/dialogflowcx_v3/services/flows/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/flows/transports/__init__.py @@ -19,15 +19,20 @@ from .base import FlowsTransport from .grpc import FlowsGrpcTransport from .grpc_asyncio import FlowsGrpcAsyncIOTransport +from .rest import FlowsRestTransport +from .rest import FlowsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[FlowsTransport]] _transport_registry["grpc"] = FlowsGrpcTransport _transport_registry["grpc_asyncio"] = FlowsGrpcAsyncIOTransport +_transport_registry["rest"] = FlowsRestTransport __all__ = ( "FlowsTransport", "FlowsGrpcTransport", "FlowsGrpcAsyncIOTransport", + "FlowsRestTransport", + "FlowsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/flows/transports/rest.py b/google/cloud/dialogflowcx_v3/services/flows/transports/rest.py new file mode 100644 index 00000000..9e012c24 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/flows/transports/rest.py @@ -0,0 +1,2043 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import flow +from google.cloud.dialogflowcx_v3.types import flow as gcdc_flow +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import FlowsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class FlowsRestInterceptor: + """Interceptor for Flows. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the FlowsRestTransport. + + .. code-block:: python + class MyCustomFlowsInterceptor(FlowsRestInterceptor): + def pre_create_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_export_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_flow_validation_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_flow_validation_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_import_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_import_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_flows(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_flows(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_train_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_train_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_flow(self, response): + logging.log(f"Received response: {response}") + return response + + transport = FlowsRestTransport(interceptor=MyCustomFlowsInterceptor()) + client = FlowsClient(transport=transport) + + + """ + + def pre_create_flow( + self, request: gcdc_flow.CreateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_flow.CreateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_create_flow(self, response: gcdc_flow.Flow) -> gcdc_flow.Flow: + """Post-rpc interceptor for create_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_delete_flow( + self, request: flow.DeleteFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.DeleteFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def pre_export_flow( + self, request: flow.ExportFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ExportFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_export_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_flow( + self, request: flow.GetFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.GetFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_flow(self, response: flow.Flow) -> flow.Flow: + """Post-rpc interceptor for get_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_flow_validation_result( + self, + request: flow.GetFlowValidationResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[flow.GetFlowValidationResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_flow_validation_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_flow_validation_result( + self, response: flow.FlowValidationResult + ) -> flow.FlowValidationResult: + """Post-rpc interceptor for get_flow_validation_result + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_import_flow( + self, request: flow.ImportFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ImportFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for import_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_import_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for import_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_flows( + self, request: flow.ListFlowsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ListFlowsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_flows + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_flows( + self, response: flow.ListFlowsResponse + ) -> flow.ListFlowsResponse: + """Post-rpc interceptor for list_flows + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_train_flow( + self, request: flow.TrainFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.TrainFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for train_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_train_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for train_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_update_flow( + self, request: gcdc_flow.UpdateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_flow.UpdateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_update_flow(self, response: gcdc_flow.Flow) -> gcdc_flow.Flow: + """Post-rpc interceptor for update_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_validate_flow( + self, request: flow.ValidateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ValidateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_validate_flow( + self, response: flow.FlowValidationResult + ) -> flow.FlowValidationResult: + """Post-rpc interceptor for validate_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class FlowsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: FlowsRestInterceptor + + +class FlowsRestTransport(FlowsTransport): + """REST backend transport for Flows. + + Service for managing [Flows][google.cloud.dialogflow.cx.v3.Flow]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[FlowsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or FlowsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateFlow(FlowsRestStub): + def __hash__(self): + return hash("CreateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_flow.CreateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_flow.Flow: + r"""Call the create flow method over HTTP. + + Args: + request (~.gcdc_flow.CreateFlowRequest): + The request object. The request message for + [Flows.CreateFlow][google.cloud.dialogflow.cx.v3.Flows.CreateFlow]. + + 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: + ~.gcdc_flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/flows", + "body": "flow", + }, + ] + request, metadata = self._interceptor.pre_create_flow(request, metadata) + pb_request = gcdc_flow.CreateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_flow.Flow() + pb_resp = gcdc_flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_flow(resp) + return resp + + class _DeleteFlow(FlowsRestStub): + def __hash__(self): + return hash("DeleteFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.DeleteFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete flow method over HTTP. + + Args: + request (~.flow.DeleteFlowRequest): + The request object. The request message for + [Flows.DeleteFlow][google.cloud.dialogflow.cx.v3.Flows.DeleteFlow]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_flow(request, metadata) + pb_request = flow.DeleteFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _ExportFlow(FlowsRestStub): + def __hash__(self): + return hash("ExportFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ExportFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export flow method over HTTP. + + Args: + request (~.flow.ExportFlowRequest): + The request object. The request message for + [Flows.ExportFlow][google.cloud.dialogflow.cx.v3.Flows.ExportFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*}:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_flow(request, metadata) + pb_request = flow.ExportFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_flow(resp) + return resp + + class _GetFlow(FlowsRestStub): + def __hash__(self): + return hash("GetFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.GetFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.Flow: + r"""Call the get flow method over HTTP. + + Args: + request (~.flow.GetFlowRequest): + The request object. The response message for + [Flows.GetFlow][google.cloud.dialogflow.cx.v3.Flows.GetFlow]. + + 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: + ~.flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*}", + }, + ] + request, metadata = self._interceptor.pre_get_flow(request, metadata) + pb_request = flow.GetFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.Flow() + pb_resp = flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_flow(resp) + return resp + + class _GetFlowValidationResult(FlowsRestStub): + def __hash__(self): + return hash("GetFlowValidationResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.GetFlowValidationResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.FlowValidationResult: + r"""Call the get flow validation + result method over HTTP. + + Args: + request (~.flow.GetFlowValidationResultRequest): + The request object. The request message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3.Flows.GetFlowValidationResult]. + + 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: + ~.flow.FlowValidationResult: + The response message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3.Flows.GetFlowValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/validationResult}", + }, + ] + request, metadata = self._interceptor.pre_get_flow_validation_result( + request, metadata + ) + pb_request = flow.GetFlowValidationResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.FlowValidationResult() + pb_resp = flow.FlowValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_flow_validation_result(resp) + return resp + + class _ImportFlow(FlowsRestStub): + def __hash__(self): + return hash("ImportFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ImportFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the import flow method over HTTP. + + Args: + request (~.flow.ImportFlowRequest): + The request object. The request message for + [Flows.ImportFlow][google.cloud.dialogflow.cx.v3.Flows.ImportFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/flows:import", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_import_flow(request, metadata) + pb_request = flow.ImportFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_import_flow(resp) + return resp + + class _ListFlows(FlowsRestStub): + def __hash__(self): + return hash("ListFlows") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ListFlowsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.ListFlowsResponse: + r"""Call the list flows method over HTTP. + + Args: + request (~.flow.ListFlowsRequest): + The request object. The request message for + [Flows.ListFlows][google.cloud.dialogflow.cx.v3.Flows.ListFlows]. + + 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: + ~.flow.ListFlowsResponse: + The response message for + [Flows.ListFlows][google.cloud.dialogflow.cx.v3.Flows.ListFlows]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/flows", + }, + ] + request, metadata = self._interceptor.pre_list_flows(request, metadata) + pb_request = flow.ListFlowsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.ListFlowsResponse() + pb_resp = flow.ListFlowsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_flows(resp) + return resp + + class _TrainFlow(FlowsRestStub): + def __hash__(self): + return hash("TrainFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.TrainFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the train flow method over HTTP. + + Args: + request (~.flow.TrainFlowRequest): + The request object. The request message for + [Flows.TrainFlow][google.cloud.dialogflow.cx.v3.Flows.TrainFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*}:train", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_train_flow(request, metadata) + pb_request = flow.TrainFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_train_flow(resp) + return resp + + class _UpdateFlow(FlowsRestStub): + def __hash__(self): + return hash("UpdateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_flow.UpdateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_flow.Flow: + r"""Call the update flow method over HTTP. + + Args: + request (~.gcdc_flow.UpdateFlowRequest): + The request object. The request message for + [Flows.UpdateFlow][google.cloud.dialogflow.cx.v3.Flows.UpdateFlow]. + + 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: + ~.gcdc_flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{flow.name=projects/*/locations/*/agents/*/flows/*}", + "body": "flow", + }, + ] + request, metadata = self._interceptor.pre_update_flow(request, metadata) + pb_request = gcdc_flow.UpdateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_flow.Flow() + pb_resp = gcdc_flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_flow(resp) + return resp + + class _ValidateFlow(FlowsRestStub): + def __hash__(self): + return hash("ValidateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ValidateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.FlowValidationResult: + r"""Call the validate flow method over HTTP. + + Args: + request (~.flow.ValidateFlowRequest): + The request object. The request message for + [Flows.ValidateFlow][google.cloud.dialogflow.cx.v3.Flows.ValidateFlow]. + + 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: + ~.flow.FlowValidationResult: + The response message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3.Flows.GetFlowValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*}:validate", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_flow(request, metadata) + pb_request = flow.ValidateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.FlowValidationResult() + pb_resp = flow.FlowValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_flow(resp) + return resp + + @property + def create_flow(self) -> Callable[[gcdc_flow.CreateFlowRequest], gcdc_flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_flow(self) -> Callable[[flow.DeleteFlowRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_flow( + self, + ) -> Callable[[flow.ExportFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_flow(self) -> Callable[[flow.GetFlowRequest], flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_flow_validation_result( + self, + ) -> Callable[[flow.GetFlowValidationResultRequest], flow.FlowValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetFlowValidationResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def import_flow( + self, + ) -> Callable[[flow.ImportFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ImportFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_flows(self) -> Callable[[flow.ListFlowsRequest], flow.ListFlowsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListFlows(self._session, self._host, self._interceptor) # type: ignore + + @property + def train_flow(self) -> Callable[[flow.TrainFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._TrainFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_flow(self) -> Callable[[gcdc_flow.UpdateFlowRequest], gcdc_flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_flow( + self, + ) -> Callable[[flow.ValidateFlowRequest], flow.FlowValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ValidateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(FlowsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(FlowsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(FlowsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(FlowsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(FlowsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("FlowsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/intents/client.py b/google/cloud/dialogflowcx_v3/services/intents/client.py index 3cf95e09..94bd1018 100644 --- a/google/cloud/dialogflowcx_v3/services/intents/client.py +++ b/google/cloud/dialogflowcx_v3/services/intents/client.py @@ -55,6 +55,7 @@ from .transports.base import IntentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import IntentsGrpcTransport from .transports.grpc_asyncio import IntentsGrpcAsyncIOTransport +from .transports.rest import IntentsRestTransport class IntentsClientMeta(type): @@ -68,6 +69,7 @@ class IntentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[IntentsTransport]] _transport_registry["grpc"] = IntentsGrpcTransport _transport_registry["grpc_asyncio"] = IntentsGrpcAsyncIOTransport + _transport_registry["rest"] = IntentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/intents/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/intents/transports/__init__.py index 876b5cd1..a7e402a6 100644 --- a/google/cloud/dialogflowcx_v3/services/intents/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/intents/transports/__init__.py @@ -19,15 +19,20 @@ from .base import IntentsTransport from .grpc import IntentsGrpcTransport from .grpc_asyncio import IntentsGrpcAsyncIOTransport +from .rest import IntentsRestTransport +from .rest import IntentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[IntentsTransport]] _transport_registry["grpc"] = IntentsGrpcTransport _transport_registry["grpc_asyncio"] = IntentsGrpcAsyncIOTransport +_transport_registry["rest"] = IntentsRestTransport __all__ = ( "IntentsTransport", "IntentsGrpcTransport", "IntentsGrpcAsyncIOTransport", + "IntentsRestTransport", + "IntentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/intents/transports/rest.py b/google/cloud/dialogflowcx_v3/services/intents/transports/rest.py new file mode 100644 index 00000000..768912ea --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/intents/transports/rest.py @@ -0,0 +1,1274 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import intent +from google.cloud.dialogflowcx_v3.types import intent as gcdc_intent +from google.protobuf import empty_pb2 # type: ignore + +from .base import IntentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class IntentsRestInterceptor: + """Interceptor for Intents. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the IntentsRestTransport. + + .. code-block:: python + class MyCustomIntentsInterceptor(IntentsRestInterceptor): + def pre_create_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_intents(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_intents(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_intent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = IntentsRestTransport(interceptor=MyCustomIntentsInterceptor()) + client = IntentsClient(transport=transport) + + + """ + + def pre_create_intent( + self, + request: gcdc_intent.CreateIntentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_intent.CreateIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_create_intent(self, response: gcdc_intent.Intent) -> gcdc_intent.Intent: + """Post-rpc interceptor for create_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_delete_intent( + self, request: intent.DeleteIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.DeleteIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def pre_get_intent( + self, request: intent.GetIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.GetIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_intent(self, response: intent.Intent) -> intent.Intent: + """Post-rpc interceptor for get_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_intents( + self, request: intent.ListIntentsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.ListIntentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_intents + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_intents( + self, response: intent.ListIntentsResponse + ) -> intent.ListIntentsResponse: + """Post-rpc interceptor for list_intents + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_update_intent( + self, + request: gcdc_intent.UpdateIntentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_intent.UpdateIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_update_intent(self, response: gcdc_intent.Intent) -> gcdc_intent.Intent: + """Post-rpc interceptor for update_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class IntentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: IntentsRestInterceptor + + +class IntentsRestTransport(IntentsTransport): + """REST backend transport for Intents. + + Service for managing + [Intents][google.cloud.dialogflow.cx.v3.Intent]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[IntentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or IntentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateIntent(IntentsRestStub): + def __hash__(self): + return hash("CreateIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_intent.CreateIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_intent.Intent: + r"""Call the create intent method over HTTP. + + Args: + request (~.gcdc_intent.CreateIntentRequest): + The request object. The request message for + [Intents.CreateIntent][google.cloud.dialogflow.cx.v3.Intents.CreateIntent]. + + 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: + ~.gcdc_intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/intents", + "body": "intent", + }, + ] + request, metadata = self._interceptor.pre_create_intent(request, metadata) + pb_request = gcdc_intent.CreateIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_intent.Intent() + pb_resp = gcdc_intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_intent(resp) + return resp + + class _DeleteIntent(IntentsRestStub): + def __hash__(self): + return hash("DeleteIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.DeleteIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete intent method over HTTP. + + Args: + request (~.intent.DeleteIntentRequest): + The request object. The request message for + [Intents.DeleteIntent][google.cloud.dialogflow.cx.v3.Intents.DeleteIntent]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/intents/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_intent(request, metadata) + pb_request = intent.DeleteIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetIntent(IntentsRestStub): + def __hash__(self): + return hash("GetIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.GetIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> intent.Intent: + r"""Call the get intent method over HTTP. + + Args: + request (~.intent.GetIntentRequest): + The request object. The request message for + [Intents.GetIntent][google.cloud.dialogflow.cx.v3.Intents.GetIntent]. + + 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: + ~.intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/intents/*}", + }, + ] + request, metadata = self._interceptor.pre_get_intent(request, metadata) + pb_request = intent.GetIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = intent.Intent() + pb_resp = intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_intent(resp) + return resp + + class _ListIntents(IntentsRestStub): + def __hash__(self): + return hash("ListIntents") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.ListIntentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> intent.ListIntentsResponse: + r"""Call the list intents method over HTTP. + + Args: + request (~.intent.ListIntentsRequest): + The request object. The request message for + [Intents.ListIntents][google.cloud.dialogflow.cx.v3.Intents.ListIntents]. + + 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: + ~.intent.ListIntentsResponse: + The response message for + [Intents.ListIntents][google.cloud.dialogflow.cx.v3.Intents.ListIntents]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/intents", + }, + ] + request, metadata = self._interceptor.pre_list_intents(request, metadata) + pb_request = intent.ListIntentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = intent.ListIntentsResponse() + pb_resp = intent.ListIntentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_intents(resp) + return resp + + class _UpdateIntent(IntentsRestStub): + def __hash__(self): + return hash("UpdateIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_intent.UpdateIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_intent.Intent: + r"""Call the update intent method over HTTP. + + Args: + request (~.gcdc_intent.UpdateIntentRequest): + The request object. The request message for + [Intents.UpdateIntent][google.cloud.dialogflow.cx.v3.Intents.UpdateIntent]. + + 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: + ~.gcdc_intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{intent.name=projects/*/locations/*/agents/*/intents/*}", + "body": "intent", + }, + ] + request, metadata = self._interceptor.pre_update_intent(request, metadata) + pb_request = gcdc_intent.UpdateIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_intent.Intent() + pb_resp = gcdc_intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_intent(resp) + return resp + + @property + def create_intent( + self, + ) -> Callable[[gcdc_intent.CreateIntentRequest], gcdc_intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_intent(self) -> Callable[[intent.DeleteIntentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_intent(self) -> Callable[[intent.GetIntentRequest], intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_intents( + self, + ) -> Callable[[intent.ListIntentsRequest], intent.ListIntentsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListIntents(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_intent( + self, + ) -> Callable[[gcdc_intent.UpdateIntentRequest], gcdc_intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(IntentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(IntentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(IntentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(IntentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(IntentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("IntentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/pages/client.py b/google/cloud/dialogflowcx_v3/services/pages/client.py index 36547c2c..cd1b0246 100644 --- a/google/cloud/dialogflowcx_v3/services/pages/client.py +++ b/google/cloud/dialogflowcx_v3/services/pages/client.py @@ -56,6 +56,7 @@ from .transports.base import PagesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import PagesGrpcTransport from .transports.grpc_asyncio import PagesGrpcAsyncIOTransport +from .transports.rest import PagesRestTransport class PagesClientMeta(type): @@ -69,6 +70,7 @@ class PagesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[PagesTransport]] _transport_registry["grpc"] = PagesGrpcTransport _transport_registry["grpc_asyncio"] = PagesGrpcAsyncIOTransport + _transport_registry["rest"] = PagesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/pages/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/pages/transports/__init__.py index fd23f06e..387c6a3a 100644 --- a/google/cloud/dialogflowcx_v3/services/pages/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/pages/transports/__init__.py @@ -19,15 +19,20 @@ from .base import PagesTransport from .grpc import PagesGrpcTransport from .grpc_asyncio import PagesGrpcAsyncIOTransport +from .rest import PagesRestTransport +from .rest import PagesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[PagesTransport]] _transport_registry["grpc"] = PagesGrpcTransport _transport_registry["grpc_asyncio"] = PagesGrpcAsyncIOTransport +_transport_registry["rest"] = PagesRestTransport __all__ = ( "PagesTransport", "PagesGrpcTransport", "PagesGrpcAsyncIOTransport", + "PagesRestTransport", + "PagesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/pages/transports/rest.py b/google/cloud/dialogflowcx_v3/services/pages/transports/rest.py new file mode 100644 index 00000000..34c6ef01 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/pages/transports/rest.py @@ -0,0 +1,1305 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import page +from google.cloud.dialogflowcx_v3.types import page as gcdc_page +from google.protobuf import empty_pb2 # type: ignore + +from .base import PagesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class PagesRestInterceptor: + """Interceptor for Pages. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the PagesRestTransport. + + .. code-block:: python + class MyCustomPagesInterceptor(PagesRestInterceptor): + def pre_create_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_page(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_page(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_pages(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_pages(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_page(self, response): + logging.log(f"Received response: {response}") + return response + + transport = PagesRestTransport(interceptor=MyCustomPagesInterceptor()) + client = PagesClient(transport=transport) + + + """ + + def pre_create_page( + self, request: gcdc_page.CreatePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_page.CreatePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_create_page(self, response: gcdc_page.Page) -> gcdc_page.Page: + """Post-rpc interceptor for create_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_delete_page( + self, request: page.DeletePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.DeletePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def pre_get_page( + self, request: page.GetPageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.GetPageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_page(self, response: page.Page) -> page.Page: + """Post-rpc interceptor for get_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_pages( + self, request: page.ListPagesRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.ListPagesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_pages + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_pages( + self, response: page.ListPagesResponse + ) -> page.ListPagesResponse: + """Post-rpc interceptor for list_pages + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_update_page( + self, request: gcdc_page.UpdatePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_page.UpdatePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_update_page(self, response: gcdc_page.Page) -> gcdc_page.Page: + """Post-rpc interceptor for update_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class PagesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: PagesRestInterceptor + + +class PagesRestTransport(PagesTransport): + """REST backend transport for Pages. + + Service for managing [Pages][google.cloud.dialogflow.cx.v3.Page]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[PagesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or PagesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreatePage(PagesRestStub): + def __hash__(self): + return hash("CreatePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_page.CreatePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_page.Page: + r"""Call the create page method over HTTP. + + Args: + request (~.gcdc_page.CreatePageRequest): + The request object. The request message for + [Pages.CreatePage][google.cloud.dialogflow.cx.v3.Pages.CreatePage]. + + 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: + ~.gcdc_page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/pages", + "body": "page", + }, + ] + request, metadata = self._interceptor.pre_create_page(request, metadata) + pb_request = gcdc_page.CreatePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_page.Page() + pb_resp = gcdc_page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_page(resp) + return resp + + class _DeletePage(PagesRestStub): + def __hash__(self): + return hash("DeletePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.DeletePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete page method over HTTP. + + Args: + request (~.page.DeletePageRequest): + The request object. The request message for + [Pages.DeletePage][google.cloud.dialogflow.cx.v3.Pages.DeletePage]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/pages/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_page(request, metadata) + pb_request = page.DeletePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetPage(PagesRestStub): + def __hash__(self): + return hash("GetPage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.GetPageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> page.Page: + r"""Call the get page method over HTTP. + + Args: + request (~.page.GetPageRequest): + The request object. The request message for + [Pages.GetPage][google.cloud.dialogflow.cx.v3.Pages.GetPage]. + + 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: + ~.page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/pages/*}", + }, + ] + request, metadata = self._interceptor.pre_get_page(request, metadata) + pb_request = page.GetPageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = page.Page() + pb_resp = page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_page(resp) + return resp + + class _ListPages(PagesRestStub): + def __hash__(self): + return hash("ListPages") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.ListPagesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> page.ListPagesResponse: + r"""Call the list pages method over HTTP. + + Args: + request (~.page.ListPagesRequest): + The request object. The request message for + [Pages.ListPages][google.cloud.dialogflow.cx.v3.Pages.ListPages]. + + 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: + ~.page.ListPagesResponse: + The response message for + [Pages.ListPages][google.cloud.dialogflow.cx.v3.Pages.ListPages]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/pages", + }, + ] + request, metadata = self._interceptor.pre_list_pages(request, metadata) + pb_request = page.ListPagesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = page.ListPagesResponse() + pb_resp = page.ListPagesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_pages(resp) + return resp + + class _UpdatePage(PagesRestStub): + def __hash__(self): + return hash("UpdatePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_page.UpdatePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_page.Page: + r"""Call the update page method over HTTP. + + Args: + request (~.gcdc_page.UpdatePageRequest): + The request object. The request message for + [Pages.UpdatePage][google.cloud.dialogflow.cx.v3.Pages.UpdatePage]. + + 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: + ~.gcdc_page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{page.name=projects/*/locations/*/agents/*/flows/*/pages/*}", + "body": "page", + }, + ] + request, metadata = self._interceptor.pre_update_page(request, metadata) + pb_request = gcdc_page.UpdatePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_page.Page() + pb_resp = gcdc_page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_page(resp) + return resp + + @property + def create_page(self) -> Callable[[gcdc_page.CreatePageRequest], gcdc_page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreatePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_page(self) -> Callable[[page.DeletePageRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeletePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_page(self) -> Callable[[page.GetPageRequest], page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetPage(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_pages(self) -> Callable[[page.ListPagesRequest], page.ListPagesResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListPages(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_page(self) -> Callable[[gcdc_page.UpdatePageRequest], gcdc_page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdatePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(PagesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(PagesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(PagesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(PagesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(PagesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("PagesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/security_settings_service/client.py b/google/cloud/dialogflowcx_v3/services/security_settings_service/client.py index 90ae8eff..7349eb12 100644 --- a/google/cloud/dialogflowcx_v3/services/security_settings_service/client.py +++ b/google/cloud/dialogflowcx_v3/services/security_settings_service/client.py @@ -57,6 +57,7 @@ from .transports.base import SecuritySettingsServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SecuritySettingsServiceGrpcTransport from .transports.grpc_asyncio import SecuritySettingsServiceGrpcAsyncIOTransport +from .transports.rest import SecuritySettingsServiceRestTransport class SecuritySettingsServiceClientMeta(type): @@ -72,6 +73,7 @@ class SecuritySettingsServiceClientMeta(type): ) # type: Dict[str, Type[SecuritySettingsServiceTransport]] _transport_registry["grpc"] = SecuritySettingsServiceGrpcTransport _transport_registry["grpc_asyncio"] = SecuritySettingsServiceGrpcAsyncIOTransport + _transport_registry["rest"] = SecuritySettingsServiceRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/__init__.py index bcf597b1..8bb194f4 100644 --- a/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/__init__.py @@ -19,6 +19,8 @@ from .base import SecuritySettingsServiceTransport from .grpc import SecuritySettingsServiceGrpcTransport from .grpc_asyncio import SecuritySettingsServiceGrpcAsyncIOTransport +from .rest import SecuritySettingsServiceRestTransport +from .rest import SecuritySettingsServiceRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[SecuritySettingsServiceTransport]] _transport_registry["grpc"] = SecuritySettingsServiceGrpcTransport _transport_registry["grpc_asyncio"] = SecuritySettingsServiceGrpcAsyncIOTransport +_transport_registry["rest"] = SecuritySettingsServiceRestTransport __all__ = ( "SecuritySettingsServiceTransport", "SecuritySettingsServiceGrpcTransport", "SecuritySettingsServiceGrpcAsyncIOTransport", + "SecuritySettingsServiceRestTransport", + "SecuritySettingsServiceRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/rest.py b/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/rest.py new file mode 100644 index 00000000..47a86abb --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/security_settings_service/transports/rest.py @@ -0,0 +1,1327 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import security_settings +from google.cloud.dialogflowcx_v3.types import ( + security_settings as gcdc_security_settings, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + SecuritySettingsServiceTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SecuritySettingsServiceRestInterceptor: + """Interceptor for SecuritySettingsService. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SecuritySettingsServiceRestTransport. + + .. code-block:: python + class MyCustomSecuritySettingsServiceInterceptor(SecuritySettingsServiceRestInterceptor): + def pre_create_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SecuritySettingsServiceRestTransport(interceptor=MyCustomSecuritySettingsServiceInterceptor()) + client = SecuritySettingsServiceClient(transport=transport) + + + """ + + def pre_create_security_settings( + self, + request: gcdc_security_settings.CreateSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_security_settings.CreateSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for create_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_create_security_settings( + self, response: gcdc_security_settings.SecuritySettings + ) -> gcdc_security_settings.SecuritySettings: + """Post-rpc interceptor for create_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_delete_security_settings( + self, + request: security_settings.DeleteSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + security_settings.DeleteSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for delete_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def pre_get_security_settings( + self, + request: security_settings.GetSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[security_settings.GetSecuritySettingsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_security_settings( + self, response: security_settings.SecuritySettings + ) -> security_settings.SecuritySettings: + """Post-rpc interceptor for get_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_security_settings( + self, + request: security_settings.ListSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + security_settings.ListSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for list_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_security_settings( + self, response: security_settings.ListSecuritySettingsResponse + ) -> security_settings.ListSecuritySettingsResponse: + """Post-rpc interceptor for list_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_update_security_settings( + self, + request: gcdc_security_settings.UpdateSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_security_settings.UpdateSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for update_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_update_security_settings( + self, response: gcdc_security_settings.SecuritySettings + ) -> gcdc_security_settings.SecuritySettings: + """Post-rpc interceptor for update_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SecuritySettingsServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SecuritySettingsServiceRestInterceptor + + +class SecuritySettingsServiceRestTransport(SecuritySettingsServiceTransport): + """REST backend transport for SecuritySettingsService. + + Service for managing security settings for Dialogflow. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SecuritySettingsServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SecuritySettingsServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("CreateSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_security_settings.CreateSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_security_settings.SecuritySettings: + r"""Call the create security settings method over HTTP. + + Args: + request (~.gcdc_security_settings.CreateSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.CreateSecuritySettings][]. + + 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: + ~.gcdc_security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*}/securitySettings", + "body": "security_settings", + }, + ] + request, metadata = self._interceptor.pre_create_security_settings( + request, metadata + ) + pb_request = gcdc_security_settings.CreateSecuritySettingsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_security_settings.SecuritySettings() + pb_resp = gcdc_security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_security_settings(resp) + return resp + + class _DeleteSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("DeleteSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.DeleteSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete security settings method over HTTP. + + Args: + request (~.security_settings.DeleteSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.DeleteSecuritySettings][]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/securitySettings/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_security_settings( + request, metadata + ) + pb_request = security_settings.DeleteSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("GetSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.GetSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> security_settings.SecuritySettings: + r"""Call the get security settings method over HTTP. + + Args: + request (~.security_settings.GetSecuritySettingsRequest): + The request object. The request message for + [SecuritySettingsService.GetSecuritySettings][google.cloud.dialogflow.cx.v3.SecuritySettingsService.GetSecuritySettings]. + + 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: + ~.security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/securitySettings/*}", + }, + ] + request, metadata = self._interceptor.pre_get_security_settings( + request, metadata + ) + pb_request = security_settings.GetSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = security_settings.SecuritySettings() + pb_resp = security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_security_settings(resp) + return resp + + class _ListSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("ListSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.ListSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> security_settings.ListSecuritySettingsResponse: + r"""Call the list security settings method over HTTP. + + Args: + request (~.security_settings.ListSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.ListSecuritySettings][]. + + 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: + ~.security_settings.ListSecuritySettingsResponse: + The response message for + [SecuritySettings.ListSecuritySettings][]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*}/securitySettings", + }, + ] + request, metadata = self._interceptor.pre_list_security_settings( + request, metadata + ) + pb_request = security_settings.ListSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = security_settings.ListSecuritySettingsResponse() + pb_resp = security_settings.ListSecuritySettingsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_security_settings(resp) + return resp + + class _UpdateSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("UpdateSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_security_settings.UpdateSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_security_settings.SecuritySettings: + r"""Call the update security settings method over HTTP. + + Args: + request (~.gcdc_security_settings.UpdateSecuritySettingsRequest): + The request object. The request message for + [SecuritySettingsService.UpdateSecuritySettings][google.cloud.dialogflow.cx.v3.SecuritySettingsService.UpdateSecuritySettings]. + + 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: + ~.gcdc_security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{security_settings.name=projects/*/locations/*/securitySettings/*}", + "body": "security_settings", + }, + ] + request, metadata = self._interceptor.pre_update_security_settings( + request, metadata + ) + pb_request = gcdc_security_settings.UpdateSecuritySettingsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_security_settings.SecuritySettings() + pb_resp = gcdc_security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_security_settings(resp) + return resp + + @property + def create_security_settings( + self, + ) -> Callable[ + [gcdc_security_settings.CreateSecuritySettingsRequest], + gcdc_security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_security_settings( + self, + ) -> Callable[[security_settings.DeleteSecuritySettingsRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_security_settings( + self, + ) -> Callable[ + [security_settings.GetSecuritySettingsRequest], + security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_security_settings( + self, + ) -> Callable[ + [security_settings.ListSecuritySettingsRequest], + security_settings.ListSecuritySettingsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_security_settings( + self, + ) -> Callable[ + [gcdc_security_settings.UpdateSecuritySettingsRequest], + gcdc_security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SecuritySettingsServiceRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SecuritySettingsServiceRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/session_entity_types/client.py b/google/cloud/dialogflowcx_v3/services/session_entity_types/client.py index 9daf6771..6bba5380 100644 --- a/google/cloud/dialogflowcx_v3/services/session_entity_types/client.py +++ b/google/cloud/dialogflowcx_v3/services/session_entity_types/client.py @@ -58,6 +58,7 @@ from .transports.base import SessionEntityTypesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SessionEntityTypesGrpcTransport from .transports.grpc_asyncio import SessionEntityTypesGrpcAsyncIOTransport +from .transports.rest import SessionEntityTypesRestTransport class SessionEntityTypesClientMeta(type): @@ -73,6 +74,7 @@ class SessionEntityTypesClientMeta(type): ) # type: Dict[str, Type[SessionEntityTypesTransport]] _transport_registry["grpc"] = SessionEntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = SessionEntityTypesGrpcAsyncIOTransport + _transport_registry["rest"] = SessionEntityTypesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/__init__.py index 44e2a8d4..688ff207 100644 --- a/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/__init__.py @@ -19,6 +19,8 @@ from .base import SessionEntityTypesTransport from .grpc import SessionEntityTypesGrpcTransport from .grpc_asyncio import SessionEntityTypesGrpcAsyncIOTransport +from .rest import SessionEntityTypesRestTransport +from .rest import SessionEntityTypesRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[SessionEntityTypesTransport]] _transport_registry["grpc"] = SessionEntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = SessionEntityTypesGrpcAsyncIOTransport +_transport_registry["rest"] = SessionEntityTypesRestTransport __all__ = ( "SessionEntityTypesTransport", "SessionEntityTypesGrpcTransport", "SessionEntityTypesGrpcAsyncIOTransport", + "SessionEntityTypesRestTransport", + "SessionEntityTypesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/rest.py b/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/rest.py new file mode 100644 index 00000000..9139ecbc --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/session_entity_types/transports/rest.py @@ -0,0 +1,1390 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import session_entity_type +from google.cloud.dialogflowcx_v3.types import ( + session_entity_type as gcdc_session_entity_type, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + SessionEntityTypesTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SessionEntityTypesRestInterceptor: + """Interceptor for SessionEntityTypes. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SessionEntityTypesRestTransport. + + .. code-block:: python + class MyCustomSessionEntityTypesInterceptor(SessionEntityTypesRestInterceptor): + def pre_create_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_session_entity_types(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_session_entity_types(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SessionEntityTypesRestTransport(interceptor=MyCustomSessionEntityTypesInterceptor()) + client = SessionEntityTypesClient(transport=transport) + + + """ + + def pre_create_session_entity_type( + self, + request: gcdc_session_entity_type.CreateSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_session_entity_type.CreateSessionEntityTypeRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for create_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_create_session_entity_type( + self, response: gcdc_session_entity_type.SessionEntityType + ) -> gcdc_session_entity_type.SessionEntityType: + """Post-rpc interceptor for create_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_delete_session_entity_type( + self, + request: session_entity_type.DeleteSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.DeleteSessionEntityTypeRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for delete_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def pre_get_session_entity_type( + self, + request: session_entity_type.GetSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.GetSessionEntityTypeRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for get_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_session_entity_type( + self, response: session_entity_type.SessionEntityType + ) -> session_entity_type.SessionEntityType: + """Post-rpc interceptor for get_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_session_entity_types( + self, + request: session_entity_type.ListSessionEntityTypesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.ListSessionEntityTypesRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for list_session_entity_types + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_session_entity_types( + self, response: session_entity_type.ListSessionEntityTypesResponse + ) -> session_entity_type.ListSessionEntityTypesResponse: + """Post-rpc interceptor for list_session_entity_types + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_update_session_entity_type( + self, + request: gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for update_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_update_session_entity_type( + self, response: gcdc_session_entity_type.SessionEntityType + ) -> gcdc_session_entity_type.SessionEntityType: + """Post-rpc interceptor for update_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SessionEntityTypesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SessionEntityTypesRestInterceptor + + +class SessionEntityTypesRestTransport(SessionEntityTypesTransport): + """REST backend transport for SessionEntityTypes. + + Service for managing + [SessionEntityTypes][google.cloud.dialogflow.cx.v3.SessionEntityType]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SessionEntityTypesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SessionEntityTypesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("CreateSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_session_entity_type.CreateSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_session_entity_type.SessionEntityType: + r"""Call the create session entity + type method over HTTP. + + Args: + request (~.gcdc_session_entity_type.CreateSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.CreateSessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityTypes.CreateSessionEntityType]. + + 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: + ~.gcdc_session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3.EntityType] at the + user session level (we refer to the entity types defined + at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes", + "body": "session_entity_type", + }, + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*/sessions/*}/entityTypes", + "body": "session_entity_type", + }, + ] + request, metadata = self._interceptor.pre_create_session_entity_type( + request, metadata + ) + pb_request = gcdc_session_entity_type.CreateSessionEntityTypeRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_session_entity_type.SessionEntityType() + pb_resp = gcdc_session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_session_entity_type(resp) + return resp + + class _DeleteSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("DeleteSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.DeleteSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete session entity + type method over HTTP. + + Args: + request (~.session_entity_type.DeleteSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.DeleteSessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityTypes.DeleteSessionEntityType]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + }, + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_session_entity_type( + request, metadata + ) + pb_request = session_entity_type.DeleteSessionEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("GetSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.GetSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session_entity_type.SessionEntityType: + r"""Call the get session entity type method over HTTP. + + Args: + request (~.session_entity_type.GetSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.GetSessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityTypes.GetSessionEntityType]. + + 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: + ~.session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3.EntityType] at the + user session level (we refer to the entity types defined + at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_get_session_entity_type( + request, metadata + ) + pb_request = session_entity_type.GetSessionEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session_entity_type.SessionEntityType() + pb_resp = session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_session_entity_type(resp) + return resp + + class _ListSessionEntityTypes(SessionEntityTypesRestStub): + def __hash__(self): + return hash("ListSessionEntityTypes") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.ListSessionEntityTypesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session_entity_type.ListSessionEntityTypesResponse: + r"""Call the list session entity types method over HTTP. + + Args: + request (~.session_entity_type.ListSessionEntityTypesRequest): + The request object. The request message for + [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.cx.v3.SessionEntityTypes.ListSessionEntityTypes]. + + 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: + ~.session_entity_type.ListSessionEntityTypesResponse: + The response message for + [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.cx.v3.SessionEntityTypes.ListSessionEntityTypes]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes", + }, + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/environments/*/sessions/*}/entityTypes", + }, + ] + request, metadata = self._interceptor.pre_list_session_entity_types( + request, metadata + ) + pb_request = session_entity_type.ListSessionEntityTypesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session_entity_type.ListSessionEntityTypesResponse() + pb_resp = session_entity_type.ListSessionEntityTypesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_session_entity_types(resp) + return resp + + class _UpdateSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("UpdateSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_session_entity_type.SessionEntityType: + r"""Call the update session entity + type method over HTTP. + + Args: + request (~.gcdc_session_entity_type.UpdateSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.UpdateSessionEntityType][google.cloud.dialogflow.cx.v3.SessionEntityTypes.UpdateSessionEntityType]. + + 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: + ~.gcdc_session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3.EntityType] at the + user session level (we refer to the entity types defined + at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{session_entity_type.name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + "body": "session_entity_type", + }, + { + "method": "patch", + "uri": "/v3/{session_entity_type.name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + "body": "session_entity_type", + }, + ] + request, metadata = self._interceptor.pre_update_session_entity_type( + request, metadata + ) + pb_request = gcdc_session_entity_type.UpdateSessionEntityTypeRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_session_entity_type.SessionEntityType() + pb_resp = gcdc_session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_session_entity_type(resp) + return resp + + @property + def create_session_entity_type( + self, + ) -> Callable[ + [gcdc_session_entity_type.CreateSessionEntityTypeRequest], + gcdc_session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_session_entity_type( + self, + ) -> Callable[ + [session_entity_type.DeleteSessionEntityTypeRequest], empty_pb2.Empty + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_session_entity_type( + self, + ) -> Callable[ + [session_entity_type.GetSessionEntityTypeRequest], + session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_session_entity_types( + self, + ) -> Callable[ + [session_entity_type.ListSessionEntityTypesRequest], + session_entity_type.ListSessionEntityTypesResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListSessionEntityTypes(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_session_entity_type( + self, + ) -> Callable[ + [gcdc_session_entity_type.UpdateSessionEntityTypeRequest], + gcdc_session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SessionEntityTypesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SessionEntityTypesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SessionEntityTypesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/sessions/client.py b/google/cloud/dialogflowcx_v3/services/sessions/client.py index c588ddc4..3339b728 100644 --- a/google/cloud/dialogflowcx_v3/services/sessions/client.py +++ b/google/cloud/dialogflowcx_v3/services/sessions/client.py @@ -56,6 +56,7 @@ from .transports.base import SessionsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SessionsGrpcTransport from .transports.grpc_asyncio import SessionsGrpcAsyncIOTransport +from .transports.rest import SessionsRestTransport class SessionsClientMeta(type): @@ -69,6 +70,7 @@ class SessionsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[SessionsTransport]] _transport_registry["grpc"] = SessionsGrpcTransport _transport_registry["grpc_asyncio"] = SessionsGrpcAsyncIOTransport + _transport_registry["rest"] = SessionsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/sessions/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/sessions/transports/__init__.py index ce00ff37..4417beb6 100644 --- a/google/cloud/dialogflowcx_v3/services/sessions/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/sessions/transports/__init__.py @@ -19,15 +19,20 @@ from .base import SessionsTransport from .grpc import SessionsGrpcTransport from .grpc_asyncio import SessionsGrpcAsyncIOTransport +from .rest import SessionsRestTransport +from .rest import SessionsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[SessionsTransport]] _transport_registry["grpc"] = SessionsGrpcTransport _transport_registry["grpc_asyncio"] = SessionsGrpcAsyncIOTransport +_transport_registry["rest"] = SessionsRestTransport __all__ = ( "SessionsTransport", "SessionsGrpcTransport", "SessionsGrpcAsyncIOTransport", + "SessionsRestTransport", + "SessionsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/sessions/transports/rest.py b/google/cloud/dialogflowcx_v3/services/sessions/transports/rest.py new file mode 100644 index 00000000..4f749a08 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/sessions/transports/rest.py @@ -0,0 +1,1069 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import session + +from .base import SessionsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SessionsRestInterceptor: + """Interceptor for Sessions. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SessionsRestTransport. + + .. code-block:: python + class MyCustomSessionsInterceptor(SessionsRestInterceptor): + def pre_detect_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_detect_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_fulfill_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_fulfill_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_match_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_match_intent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SessionsRestTransport(interceptor=MyCustomSessionsInterceptor()) + client = SessionsClient(transport=transport) + + + """ + + def pre_detect_intent( + self, request: session.DetectIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.DetectIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for detect_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_detect_intent( + self, response: session.DetectIntentResponse + ) -> session.DetectIntentResponse: + """Post-rpc interceptor for detect_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_fulfill_intent( + self, request: session.FulfillIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.FulfillIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for fulfill_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_fulfill_intent( + self, response: session.FulfillIntentResponse + ) -> session.FulfillIntentResponse: + """Post-rpc interceptor for fulfill_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_match_intent( + self, request: session.MatchIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.MatchIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for match_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_match_intent( + self, response: session.MatchIntentResponse + ) -> session.MatchIntentResponse: + """Post-rpc interceptor for match_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SessionsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SessionsRestInterceptor + + +class SessionsRestTransport(SessionsTransport): + """REST backend transport for Sessions. + + A session represents an interaction with a user. You retrieve user + input and pass it to the + [DetectIntent][google.cloud.dialogflow.cx.v3.Sessions.DetectIntent] + method to determine user intent and respond. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SessionsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SessionsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _DetectIntent(SessionsRestStub): + def __hash__(self): + return hash("DetectIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session.DetectIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.DetectIntentResponse: + r"""Call the detect intent method over HTTP. + + Args: + request (~.session.DetectIntentRequest): + The request object. The request to detect user's intent. + 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: + ~.session.DetectIntentResponse: + The message returned from the + DetectIntent method. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{session=projects/*/locations/*/agents/*/sessions/*}:detectIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3/{session=projects/*/locations/*/agents/*/environments/*/sessions/*}:detectIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_detect_intent(request, metadata) + pb_request = session.DetectIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.DetectIntentResponse() + pb_resp = session.DetectIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_detect_intent(resp) + return resp + + class _FulfillIntent(SessionsRestStub): + def __hash__(self): + return hash("FulfillIntent") + + def __call__( + self, + request: session.FulfillIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.FulfillIntentResponse: + r"""Call the fulfill intent method over HTTP. + + Args: + request (~.session.FulfillIntentRequest): + The request object. Request of [FulfillIntent][] + 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: + ~.session.FulfillIntentResponse: + Response of [FulfillIntent][] + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{match_intent_request.session=projects/*/locations/*/agents/*/sessions/*}:fulfillIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3/{match_intent_request.session=projects/*/locations/*/agents/*/environments/*/sessions/*}:fulfillIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_fulfill_intent(request, metadata) + pb_request = session.FulfillIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.FulfillIntentResponse() + pb_resp = session.FulfillIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_fulfill_intent(resp) + return resp + + class _MatchIntent(SessionsRestStub): + def __hash__(self): + return hash("MatchIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session.MatchIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.MatchIntentResponse: + r"""Call the match intent method over HTTP. + + Args: + request (~.session.MatchIntentRequest): + The request object. Request of [MatchIntent][]. + 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: + ~.session.MatchIntentResponse: + Response of [MatchIntent][]. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{session=projects/*/locations/*/agents/*/sessions/*}:matchIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3/{session=projects/*/locations/*/agents/*/environments/*/sessions/*}:matchIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_match_intent(request, metadata) + pb_request = session.MatchIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.MatchIntentResponse() + pb_resp = session.MatchIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_match_intent(resp) + return resp + + class _StreamingDetectIntent(SessionsRestStub): + def __hash__(self): + return hash("StreamingDetectIntent") + + def __call__( + self, + request: session.StreamingDetectIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> rest_streaming.ResponseIterator: + raise NotImplementedError( + "Method StreamingDetectIntent is not available over REST transport" + ) + + @property + def detect_intent( + self, + ) -> Callable[[session.DetectIntentRequest], session.DetectIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DetectIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def fulfill_intent( + self, + ) -> Callable[[session.FulfillIntentRequest], session.FulfillIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._FulfillIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def match_intent( + self, + ) -> Callable[[session.MatchIntentRequest], session.MatchIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._MatchIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def streaming_detect_intent( + self, + ) -> Callable[ + [session.StreamingDetectIntentRequest], session.StreamingDetectIntentResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StreamingDetectIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SessionsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SessionsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SessionsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SessionsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SessionsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SessionsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/test_cases/client.py b/google/cloud/dialogflowcx_v3/services/test_cases/client.py index 9ff4cc8a..7387bd63 100644 --- a/google/cloud/dialogflowcx_v3/services/test_cases/client.py +++ b/google/cloud/dialogflowcx_v3/services/test_cases/client.py @@ -58,6 +58,7 @@ from .transports.base import TestCasesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TestCasesGrpcTransport from .transports.grpc_asyncio import TestCasesGrpcAsyncIOTransport +from .transports.rest import TestCasesRestTransport class TestCasesClientMeta(type): @@ -71,6 +72,7 @@ class TestCasesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[TestCasesTransport]] _transport_registry["grpc"] = TestCasesGrpcTransport _transport_registry["grpc_asyncio"] = TestCasesGrpcAsyncIOTransport + _transport_registry["rest"] = TestCasesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/test_cases/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/test_cases/transports/__init__.py index 10d2c977..0b296261 100644 --- a/google/cloud/dialogflowcx_v3/services/test_cases/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/test_cases/transports/__init__.py @@ -19,15 +19,20 @@ from .base import TestCasesTransport from .grpc import TestCasesGrpcTransport from .grpc_asyncio import TestCasesGrpcAsyncIOTransport +from .rest import TestCasesRestTransport +from .rest import TestCasesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[TestCasesTransport]] _transport_registry["grpc"] = TestCasesGrpcTransport _transport_registry["grpc_asyncio"] = TestCasesGrpcAsyncIOTransport +_transport_registry["rest"] = TestCasesRestTransport __all__ = ( "TestCasesTransport", "TestCasesGrpcTransport", "TestCasesGrpcAsyncIOTransport", + "TestCasesRestTransport", + "TestCasesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/test_cases/transports/rest.py b/google/cloud/dialogflowcx_v3/services/test_cases/transports/rest.py new file mode 100644 index 00000000..8bd0f4ed --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/test_cases/transports/rest.py @@ -0,0 +1,2295 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import test_case +from google.cloud.dialogflowcx_v3.types import test_case as gcdc_test_case +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import TestCasesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class TestCasesRestInterceptor: + """Interceptor for TestCases. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the TestCasesRestTransport. + + .. code-block:: python + class MyCustomTestCasesInterceptor(TestCasesRestInterceptor): + def pre_batch_delete_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_batch_run_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_batch_run_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_calculate_coverage(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_calculate_coverage(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_export_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_test_case_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_test_case_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_import_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_import_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_test_case_results(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_test_case_results(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_run_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_run_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + transport = TestCasesRestTransport(interceptor=MyCustomTestCasesInterceptor()) + client = TestCasesClient(transport=transport) + + + """ + + def pre_batch_delete_test_cases( + self, + request: test_case.BatchDeleteTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.BatchDeleteTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for batch_delete_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def pre_batch_run_test_cases( + self, + request: test_case.BatchRunTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.BatchRunTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for batch_run_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_batch_run_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for batch_run_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_calculate_coverage( + self, + request: test_case.CalculateCoverageRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.CalculateCoverageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for calculate_coverage + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_calculate_coverage( + self, response: test_case.CalculateCoverageResponse + ) -> test_case.CalculateCoverageResponse: + """Post-rpc interceptor for calculate_coverage + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_create_test_case( + self, + request: gcdc_test_case.CreateTestCaseRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_test_case.CreateTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_create_test_case( + self, response: gcdc_test_case.TestCase + ) -> gcdc_test_case.TestCase: + """Post-rpc interceptor for create_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_export_test_cases( + self, + request: test_case.ExportTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ExportTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_export_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_test_case( + self, request: test_case.GetTestCaseRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[test_case.GetTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_test_case(self, response: test_case.TestCase) -> test_case.TestCase: + """Post-rpc interceptor for get_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_test_case_result( + self, + request: test_case.GetTestCaseResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.GetTestCaseResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_test_case_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_test_case_result( + self, response: test_case.TestCaseResult + ) -> test_case.TestCaseResult: + """Post-rpc interceptor for get_test_case_result + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_import_test_cases( + self, + request: test_case.ImportTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ImportTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for import_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_import_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for import_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_test_case_results( + self, + request: test_case.ListTestCaseResultsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ListTestCaseResultsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_test_case_results + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_test_case_results( + self, response: test_case.ListTestCaseResultsResponse + ) -> test_case.ListTestCaseResultsResponse: + """Post-rpc interceptor for list_test_case_results + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_test_cases( + self, + request: test_case.ListTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ListTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_test_cases( + self, response: test_case.ListTestCasesResponse + ) -> test_case.ListTestCasesResponse: + """Post-rpc interceptor for list_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_run_test_case( + self, request: test_case.RunTestCaseRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[test_case.RunTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for run_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_run_test_case( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for run_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_update_test_case( + self, + request: gcdc_test_case.UpdateTestCaseRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_test_case.UpdateTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_update_test_case( + self, response: gcdc_test_case.TestCase + ) -> gcdc_test_case.TestCase: + """Post-rpc interceptor for update_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class TestCasesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: TestCasesRestInterceptor + + +class TestCasesRestTransport(TestCasesTransport): + """REST backend transport for TestCases. + + Service for managing [Test + Cases][google.cloud.dialogflow.cx.v3.TestCase] and [Test Case + Results][google.cloud.dialogflow.cx.v3.TestCaseResult]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[TestCasesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or TestCasesRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _BatchDeleteTestCases(TestCasesRestStub): + def __hash__(self): + return hash("BatchDeleteTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.BatchDeleteTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the batch delete test cases method over HTTP. + + Args: + request (~.test_case.BatchDeleteTestCasesRequest): + The request object. The request message for + [TestCases.BatchDeleteTestCases][google.cloud.dialogflow.cx.v3.TestCases.BatchDeleteTestCases]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases:batchDelete", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_batch_delete_test_cases( + request, metadata + ) + pb_request = test_case.BatchDeleteTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _BatchRunTestCases(TestCasesRestStub): + def __hash__(self): + return hash("BatchRunTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.BatchRunTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the batch run test cases method over HTTP. + + Args: + request (~.test_case.BatchRunTestCasesRequest): + The request object. The request message for + [TestCases.BatchRunTestCases][google.cloud.dialogflow.cx.v3.TestCases.BatchRunTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases:batchRun", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_batch_run_test_cases( + request, metadata + ) + pb_request = test_case.BatchRunTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_batch_run_test_cases(resp) + return resp + + class _CalculateCoverage(TestCasesRestStub): + def __hash__(self): + return hash("CalculateCoverage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "type": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.CalculateCoverageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.CalculateCoverageResponse: + r"""Call the calculate coverage method over HTTP. + + Args: + request (~.test_case.CalculateCoverageRequest): + The request object. The request message for + [TestCases.CalculateCoverage][google.cloud.dialogflow.cx.v3.TestCases.CalculateCoverage]. + + 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: + ~.test_case.CalculateCoverageResponse: + The response message for + [TestCases.CalculateCoverage][google.cloud.dialogflow.cx.v3.TestCases.CalculateCoverage]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{agent=projects/*/locations/*/agents/*}/testCases:calculateCoverage", + }, + ] + request, metadata = self._interceptor.pre_calculate_coverage( + request, metadata + ) + pb_request = test_case.CalculateCoverageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.CalculateCoverageResponse() + pb_resp = test_case.CalculateCoverageResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_calculate_coverage(resp) + return resp + + class _CreateTestCase(TestCasesRestStub): + def __hash__(self): + return hash("CreateTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_test_case.CreateTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_test_case.TestCase: + r"""Call the create test case method over HTTP. + + Args: + request (~.gcdc_test_case.CreateTestCaseRequest): + The request object. The request message for + [TestCases.CreateTestCase][google.cloud.dialogflow.cx.v3.TestCases.CreateTestCase]. + + 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: + ~.gcdc_test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases", + "body": "test_case", + }, + ] + request, metadata = self._interceptor.pre_create_test_case( + request, metadata + ) + pb_request = gcdc_test_case.CreateTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_test_case.TestCase() + pb_resp = gcdc_test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_test_case(resp) + return resp + + class _ExportTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ExportTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ExportTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export test cases method over HTTP. + + Args: + request (~.test_case.ExportTestCasesRequest): + The request object. The request message for + [TestCases.ExportTestCases][google.cloud.dialogflow.cx.v3.TestCases.ExportTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_test_cases( + request, metadata + ) + pb_request = test_case.ExportTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_test_cases(resp) + return resp + + class _GetTestCase(TestCasesRestStub): + def __hash__(self): + return hash("GetTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.GetTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.TestCase: + r"""Call the get test case method over HTTP. + + Args: + request (~.test_case.GetTestCaseRequest): + The request object. The request message for + [TestCases.GetTestCase][google.cloud.dialogflow.cx.v3.TestCases.GetTestCase]. + + 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: + ~.test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/testCases/*}", + }, + ] + request, metadata = self._interceptor.pre_get_test_case(request, metadata) + pb_request = test_case.GetTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.TestCase() + pb_resp = test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_test_case(resp) + return resp + + class _GetTestCaseResult(TestCasesRestStub): + def __hash__(self): + return hash("GetTestCaseResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.GetTestCaseResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.TestCaseResult: + r"""Call the get test case result method over HTTP. + + Args: + request (~.test_case.GetTestCaseResultRequest): + The request object. The request message for + [TestCases.GetTestCaseResult][google.cloud.dialogflow.cx.v3.TestCases.GetTestCaseResult]. + + 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: + ~.test_case.TestCaseResult: + Represents a result from running a + test case in an agent environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/testCases/*/results/*}", + }, + ] + request, metadata = self._interceptor.pre_get_test_case_result( + request, metadata + ) + pb_request = test_case.GetTestCaseResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.TestCaseResult() + pb_resp = test_case.TestCaseResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_test_case_result(resp) + return resp + + class _ImportTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ImportTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ImportTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the import test cases method over HTTP. + + Args: + request (~.test_case.ImportTestCasesRequest): + The request object. The request message for + [TestCases.ImportTestCases][google.cloud.dialogflow.cx.v3.TestCases.ImportTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases:import", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_import_test_cases( + request, metadata + ) + pb_request = test_case.ImportTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_import_test_cases(resp) + return resp + + class _ListTestCaseResults(TestCasesRestStub): + def __hash__(self): + return hash("ListTestCaseResults") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ListTestCaseResultsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.ListTestCaseResultsResponse: + r"""Call the list test case results method over HTTP. + + Args: + request (~.test_case.ListTestCaseResultsRequest): + The request object. The request message for + [TestCases.ListTestCaseResults][google.cloud.dialogflow.cx.v3.TestCases.ListTestCaseResults]. + + 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: + ~.test_case.ListTestCaseResultsResponse: + The response message for + [TestCases.ListTestCaseResults][google.cloud.dialogflow.cx.v3.TestCases.ListTestCaseResults]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/testCases/*}/results", + }, + ] + request, metadata = self._interceptor.pre_list_test_case_results( + request, metadata + ) + pb_request = test_case.ListTestCaseResultsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.ListTestCaseResultsResponse() + pb_resp = test_case.ListTestCaseResultsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_test_case_results(resp) + return resp + + class _ListTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ListTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ListTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.ListTestCasesResponse: + r"""Call the list test cases method over HTTP. + + Args: + request (~.test_case.ListTestCasesRequest): + The request object. The request message for + [TestCases.ListTestCases][google.cloud.dialogflow.cx.v3.TestCases.ListTestCases]. + + 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: + ~.test_case.ListTestCasesResponse: + The response message for + [TestCases.ListTestCases][google.cloud.dialogflow.cx.v3.TestCases.ListTestCases]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/testCases", + }, + ] + request, metadata = self._interceptor.pre_list_test_cases(request, metadata) + pb_request = test_case.ListTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.ListTestCasesResponse() + pb_resp = test_case.ListTestCasesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_test_cases(resp) + return resp + + class _RunTestCase(TestCasesRestStub): + def __hash__(self): + return hash("RunTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.RunTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the run test case method over HTTP. + + Args: + request (~.test_case.RunTestCaseRequest): + The request object. The request message for + [TestCases.RunTestCase][google.cloud.dialogflow.cx.v3.TestCases.RunTestCase]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/testCases/*}:run", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_run_test_case(request, metadata) + pb_request = test_case.RunTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_run_test_case(resp) + return resp + + class _UpdateTestCase(TestCasesRestStub): + def __hash__(self): + return hash("UpdateTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_test_case.UpdateTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_test_case.TestCase: + r"""Call the update test case method over HTTP. + + Args: + request (~.gcdc_test_case.UpdateTestCaseRequest): + The request object. The request message for + [TestCases.UpdateTestCase][google.cloud.dialogflow.cx.v3.TestCases.UpdateTestCase]. + + 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: + ~.gcdc_test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{test_case.name=projects/*/locations/*/agents/*/testCases/*}", + "body": "test_case", + }, + ] + request, metadata = self._interceptor.pre_update_test_case( + request, metadata + ) + pb_request = gcdc_test_case.UpdateTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_test_case.TestCase() + pb_resp = gcdc_test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_test_case(resp) + return resp + + @property + def batch_delete_test_cases( + self, + ) -> Callable[[test_case.BatchDeleteTestCasesRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._BatchDeleteTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def batch_run_test_cases( + self, + ) -> Callable[[test_case.BatchRunTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._BatchRunTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def calculate_coverage( + self, + ) -> Callable[ + [test_case.CalculateCoverageRequest], test_case.CalculateCoverageResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CalculateCoverage(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_test_case( + self, + ) -> Callable[[gcdc_test_case.CreateTestCaseRequest], gcdc_test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_test_cases( + self, + ) -> Callable[[test_case.ExportTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_test_case( + self, + ) -> Callable[[test_case.GetTestCaseRequest], test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_test_case_result( + self, + ) -> Callable[[test_case.GetTestCaseResultRequest], test_case.TestCaseResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTestCaseResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def import_test_cases( + self, + ) -> Callable[[test_case.ImportTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ImportTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_test_case_results( + self, + ) -> Callable[ + [test_case.ListTestCaseResultsRequest], test_case.ListTestCaseResultsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTestCaseResults(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_test_cases( + self, + ) -> Callable[[test_case.ListTestCasesRequest], test_case.ListTestCasesResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def run_test_case( + self, + ) -> Callable[[test_case.RunTestCaseRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RunTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_test_case( + self, + ) -> Callable[[gcdc_test_case.UpdateTestCaseRequest], gcdc_test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(TestCasesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(TestCasesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("TestCasesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/transition_route_groups/client.py b/google/cloud/dialogflowcx_v3/services/transition_route_groups/client.py index ad244522..13d04425 100644 --- a/google/cloud/dialogflowcx_v3/services/transition_route_groups/client.py +++ b/google/cloud/dialogflowcx_v3/services/transition_route_groups/client.py @@ -58,6 +58,7 @@ from .transports.base import TransitionRouteGroupsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TransitionRouteGroupsGrpcTransport from .transports.grpc_asyncio import TransitionRouteGroupsGrpcAsyncIOTransport +from .transports.rest import TransitionRouteGroupsRestTransport class TransitionRouteGroupsClientMeta(type): @@ -73,6 +74,7 @@ class TransitionRouteGroupsClientMeta(type): ) # type: Dict[str, Type[TransitionRouteGroupsTransport]] _transport_registry["grpc"] = TransitionRouteGroupsGrpcTransport _transport_registry["grpc_asyncio"] = TransitionRouteGroupsGrpcAsyncIOTransport + _transport_registry["rest"] = TransitionRouteGroupsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/__init__.py index ed445782..a9ee37b8 100644 --- a/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/__init__.py @@ -19,6 +19,8 @@ from .base import TransitionRouteGroupsTransport from .grpc import TransitionRouteGroupsGrpcTransport from .grpc_asyncio import TransitionRouteGroupsGrpcAsyncIOTransport +from .rest import TransitionRouteGroupsRestTransport +from .rest import TransitionRouteGroupsRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[TransitionRouteGroupsTransport]] _transport_registry["grpc"] = TransitionRouteGroupsGrpcTransport _transport_registry["grpc_asyncio"] = TransitionRouteGroupsGrpcAsyncIOTransport +_transport_registry["rest"] = TransitionRouteGroupsRestTransport __all__ = ( "TransitionRouteGroupsTransport", "TransitionRouteGroupsGrpcTransport", "TransitionRouteGroupsGrpcAsyncIOTransport", + "TransitionRouteGroupsRestTransport", + "TransitionRouteGroupsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/rest.py b/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/rest.py new file mode 100644 index 00000000..fe8864f9 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/transition_route_groups/transports/rest.py @@ -0,0 +1,1343 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import transition_route_group +from google.cloud.dialogflowcx_v3.types import ( + transition_route_group as gcdc_transition_route_group, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + TransitionRouteGroupsTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class TransitionRouteGroupsRestInterceptor: + """Interceptor for TransitionRouteGroups. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the TransitionRouteGroupsRestTransport. + + .. code-block:: python + class MyCustomTransitionRouteGroupsInterceptor(TransitionRouteGroupsRestInterceptor): + def pre_create_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_transition_route_groups(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_transition_route_groups(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + transport = TransitionRouteGroupsRestTransport(interceptor=MyCustomTransitionRouteGroupsInterceptor()) + client = TransitionRouteGroupsClient(transport=transport) + + + """ + + def pre_create_transition_route_group( + self, + request: gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for create_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_create_transition_route_group( + self, response: gcdc_transition_route_group.TransitionRouteGroup + ) -> gcdc_transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for create_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_delete_transition_route_group( + self, + request: transition_route_group.DeleteTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.DeleteTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for delete_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def pre_get_transition_route_group( + self, + request: transition_route_group.GetTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.GetTransitionRouteGroupRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for get_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_transition_route_group( + self, response: transition_route_group.TransitionRouteGroup + ) -> transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for get_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_transition_route_groups( + self, + request: transition_route_group.ListTransitionRouteGroupsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.ListTransitionRouteGroupsRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for list_transition_route_groups + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_transition_route_groups( + self, response: transition_route_group.ListTransitionRouteGroupsResponse + ) -> transition_route_group.ListTransitionRouteGroupsResponse: + """Post-rpc interceptor for list_transition_route_groups + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_update_transition_route_group( + self, + request: gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for update_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_update_transition_route_group( + self, response: gcdc_transition_route_group.TransitionRouteGroup + ) -> gcdc_transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for update_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class TransitionRouteGroupsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: TransitionRouteGroupsRestInterceptor + + +class TransitionRouteGroupsRestTransport(TransitionRouteGroupsTransport): + """REST backend transport for TransitionRouteGroups. + + Service for managing + [TransitionRouteGroups][google.cloud.dialogflow.cx.v3.TransitionRouteGroup]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[TransitionRouteGroupsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or TransitionRouteGroupsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("CreateTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_transition_route_group.TransitionRouteGroup: + r"""Call the create transition route + group method over HTTP. + + Args: + request (~.gcdc_transition_route_group.CreateTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.CreateTransitionRouteGroup][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.CreateTransitionRouteGroup]. + + 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: + ~.gcdc_transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups", + "body": "transition_route_group", + }, + ] + request, metadata = self._interceptor.pre_create_transition_route_group( + request, metadata + ) + pb_request = ( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest.pb( + request + ) + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_transition_route_group.TransitionRouteGroup() + pb_resp = gcdc_transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_transition_route_group(resp) + return resp + + class _DeleteTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("DeleteTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.DeleteTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete transition route + group method over HTTP. + + Args: + request (~.transition_route_group.DeleteTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.DeleteTransitionRouteGroup][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.DeleteTransitionRouteGroup]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_transition_route_group( + request, metadata + ) + pb_request = transition_route_group.DeleteTransitionRouteGroupRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("GetTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.GetTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> transition_route_group.TransitionRouteGroup: + r"""Call the get transition route + group method over HTTP. + + Args: + request (~.transition_route_group.GetTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.GetTransitionRouteGroup][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.GetTransitionRouteGroup]. + + 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: + ~.transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + }, + ] + request, metadata = self._interceptor.pre_get_transition_route_group( + request, metadata + ) + pb_request = transition_route_group.GetTransitionRouteGroupRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = transition_route_group.TransitionRouteGroup() + pb_resp = transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_transition_route_group(resp) + return resp + + class _ListTransitionRouteGroups(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("ListTransitionRouteGroups") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.ListTransitionRouteGroupsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> transition_route_group.ListTransitionRouteGroupsResponse: + r"""Call the list transition route + groups method over HTTP. + + Args: + request (~.transition_route_group.ListTransitionRouteGroupsRequest): + The request object. The request message for + [TransitionRouteGroups.ListTransitionRouteGroups][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.ListTransitionRouteGroups]. + + 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: + ~.transition_route_group.ListTransitionRouteGroupsResponse: + The response message for + [TransitionRouteGroups.ListTransitionRouteGroups][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.ListTransitionRouteGroups]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups", + }, + ] + request, metadata = self._interceptor.pre_list_transition_route_groups( + request, metadata + ) + pb_request = transition_route_group.ListTransitionRouteGroupsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = transition_route_group.ListTransitionRouteGroupsResponse() + pb_resp = transition_route_group.ListTransitionRouteGroupsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_transition_route_groups(resp) + return resp + + class _UpdateTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("UpdateTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_transition_route_group.TransitionRouteGroup: + r"""Call the update transition route + group method over HTTP. + + Args: + request (~.gcdc_transition_route_group.UpdateTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.UpdateTransitionRouteGroup][google.cloud.dialogflow.cx.v3.TransitionRouteGroups.UpdateTransitionRouteGroup]. + + 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: + ~.gcdc_transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{transition_route_group.name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + "body": "transition_route_group", + }, + ] + request, metadata = self._interceptor.pre_update_transition_route_group( + request, metadata + ) + pb_request = ( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest.pb( + request + ) + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_transition_route_group.TransitionRouteGroup() + pb_resp = gcdc_transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_transition_route_group(resp) + return resp + + @property + def create_transition_route_group( + self, + ) -> Callable[ + [gcdc_transition_route_group.CreateTransitionRouteGroupRequest], + gcdc_transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_transition_route_group( + self, + ) -> Callable[ + [transition_route_group.DeleteTransitionRouteGroupRequest], empty_pb2.Empty + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_transition_route_group( + self, + ) -> Callable[ + [transition_route_group.GetTransitionRouteGroupRequest], + transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_transition_route_groups( + self, + ) -> Callable[ + [transition_route_group.ListTransitionRouteGroupsRequest], + transition_route_group.ListTransitionRouteGroupsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTransitionRouteGroups(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_transition_route_group( + self, + ) -> Callable[ + [gcdc_transition_route_group.UpdateTransitionRouteGroupRequest], + gcdc_transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(TransitionRouteGroupsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("TransitionRouteGroupsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/versions/client.py b/google/cloud/dialogflowcx_v3/services/versions/client.py index 1f816f46..d3cd81bc 100644 --- a/google/cloud/dialogflowcx_v3/services/versions/client.py +++ b/google/cloud/dialogflowcx_v3/services/versions/client.py @@ -61,6 +61,7 @@ from .transports.base import VersionsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import VersionsGrpcTransport from .transports.grpc_asyncio import VersionsGrpcAsyncIOTransport +from .transports.rest import VersionsRestTransport class VersionsClientMeta(type): @@ -74,6 +75,7 @@ class VersionsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[VersionsTransport]] _transport_registry["grpc"] = VersionsGrpcTransport _transport_registry["grpc_asyncio"] = VersionsGrpcAsyncIOTransport + _transport_registry["rest"] = VersionsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/versions/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/versions/transports/__init__.py index ab80b3b5..780f6ec5 100644 --- a/google/cloud/dialogflowcx_v3/services/versions/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/versions/transports/__init__.py @@ -19,15 +19,20 @@ from .base import VersionsTransport from .grpc import VersionsGrpcTransport from .grpc_asyncio import VersionsGrpcAsyncIOTransport +from .rest import VersionsRestTransport +from .rest import VersionsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[VersionsTransport]] _transport_registry["grpc"] = VersionsGrpcTransport _transport_registry["grpc_asyncio"] = VersionsGrpcAsyncIOTransport +_transport_registry["rest"] = VersionsRestTransport __all__ = ( "VersionsTransport", "VersionsGrpcTransport", "VersionsGrpcAsyncIOTransport", + "VersionsRestTransport", + "VersionsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/versions/transports/rest.py b/google/cloud/dialogflowcx_v3/services/versions/transports/rest.py new file mode 100644 index 00000000..cefc398b --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/versions/transports/rest.py @@ -0,0 +1,1598 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import version +from google.cloud.dialogflowcx_v3.types import version as gcdc_version +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import VersionsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class VersionsRestInterceptor: + """Interceptor for Versions. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the VersionsRestTransport. + + .. code-block:: python + class MyCustomVersionsInterceptor(VersionsRestInterceptor): + def pre_compare_versions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_compare_versions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_versions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_versions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_load_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_load_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_version(self, response): + logging.log(f"Received response: {response}") + return response + + transport = VersionsRestTransport(interceptor=MyCustomVersionsInterceptor()) + client = VersionsClient(transport=transport) + + + """ + + def pre_compare_versions( + self, + request: version.CompareVersionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[version.CompareVersionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for compare_versions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_compare_versions( + self, response: version.CompareVersionsResponse + ) -> version.CompareVersionsResponse: + """Post-rpc interceptor for compare_versions + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_create_version( + self, + request: gcdc_version.CreateVersionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_version.CreateVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_create_version( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for create_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_delete_version( + self, request: version.DeleteVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.DeleteVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def pre_get_version( + self, request: version.GetVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.GetVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_version(self, response: version.Version) -> version.Version: + """Post-rpc interceptor for get_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_versions( + self, request: version.ListVersionsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.ListVersionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_versions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_versions( + self, response: version.ListVersionsResponse + ) -> version.ListVersionsResponse: + """Post-rpc interceptor for list_versions + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_load_version( + self, request: version.LoadVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.LoadVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for load_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_load_version( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for load_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_update_version( + self, + request: gcdc_version.UpdateVersionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_version.UpdateVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_update_version( + self, response: gcdc_version.Version + ) -> gcdc_version.Version: + """Post-rpc interceptor for update_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class VersionsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: VersionsRestInterceptor + + +class VersionsRestTransport(VersionsTransport): + """REST backend transport for Versions. + + Service for managing + [Versions][google.cloud.dialogflow.cx.v3.Version]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[VersionsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or VersionsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CompareVersions(VersionsRestStub): + def __hash__(self): + return hash("CompareVersions") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.CompareVersionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.CompareVersionsResponse: + r"""Call the compare versions method over HTTP. + + Args: + request (~.version.CompareVersionsRequest): + The request object. The request message for + [Versions.CompareVersions][google.cloud.dialogflow.cx.v3.Versions.CompareVersions]. + + 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: + ~.version.CompareVersionsResponse: + The response message for + [Versions.CompareVersions][google.cloud.dialogflow.cx.v3.Versions.CompareVersions]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{base_version=projects/*/locations/*/agents/*/flows/*/versions/*}:compareVersions", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_compare_versions( + request, metadata + ) + pb_request = version.CompareVersionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.CompareVersionsResponse() + pb_resp = version.CompareVersionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_compare_versions(resp) + return resp + + class _CreateVersion(VersionsRestStub): + def __hash__(self): + return hash("CreateVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_version.CreateVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the create version method over HTTP. + + Args: + request (~.gcdc_version.CreateVersionRequest): + The request object. The request message for + [Versions.CreateVersion][google.cloud.dialogflow.cx.v3.Versions.CreateVersion]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/versions", + "body": "version", + }, + ] + request, metadata = self._interceptor.pre_create_version(request, metadata) + pb_request = gcdc_version.CreateVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_version(resp) + return resp + + class _DeleteVersion(VersionsRestStub): + def __hash__(self): + return hash("DeleteVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.DeleteVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete version method over HTTP. + + Args: + request (~.version.DeleteVersionRequest): + The request object. The request message for + [Versions.DeleteVersion][google.cloud.dialogflow.cx.v3.Versions.DeleteVersion]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_version(request, metadata) + pb_request = version.DeleteVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetVersion(VersionsRestStub): + def __hash__(self): + return hash("GetVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.GetVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.Version: + r"""Call the get version method over HTTP. + + Args: + request (~.version.GetVersionRequest): + The request object. The request message for + [Versions.GetVersion][google.cloud.dialogflow.cx.v3.Versions.GetVersion]. + + 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: + ~.version.Version: + Represents a version of a flow. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}", + }, + ] + request, metadata = self._interceptor.pre_get_version(request, metadata) + pb_request = version.GetVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.Version() + pb_resp = version.Version.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_version(resp) + return resp + + class _ListVersions(VersionsRestStub): + def __hash__(self): + return hash("ListVersions") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.ListVersionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.ListVersionsResponse: + r"""Call the list versions method over HTTP. + + Args: + request (~.version.ListVersionsRequest): + The request object. The request message for + [Versions.ListVersions][google.cloud.dialogflow.cx.v3.Versions.ListVersions]. + + 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: + ~.version.ListVersionsResponse: + The response message for + [Versions.ListVersions][google.cloud.dialogflow.cx.v3.Versions.ListVersions]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*/flows/*}/versions", + }, + ] + request, metadata = self._interceptor.pre_list_versions(request, metadata) + pb_request = version.ListVersionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.ListVersionsResponse() + pb_resp = version.ListVersionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_versions(resp) + return resp + + class _LoadVersion(VersionsRestStub): + def __hash__(self): + return hash("LoadVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.LoadVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the load version method over HTTP. + + Args: + request (~.version.LoadVersionRequest): + The request object. The request message for + [Versions.LoadVersion][google.cloud.dialogflow.cx.v3.Versions.LoadVersion]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}:load", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_load_version(request, metadata) + pb_request = version.LoadVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_load_version(resp) + return resp + + class _UpdateVersion(VersionsRestStub): + def __hash__(self): + return hash("UpdateVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_version.UpdateVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_version.Version: + r"""Call the update version method over HTTP. + + Args: + request (~.gcdc_version.UpdateVersionRequest): + The request object. The request message for + [Versions.UpdateVersion][google.cloud.dialogflow.cx.v3.Versions.UpdateVersion]. + + 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: + ~.gcdc_version.Version: + Represents a version of a flow. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{version.name=projects/*/locations/*/agents/*/flows/*/versions/*}", + "body": "version", + }, + ] + request, metadata = self._interceptor.pre_update_version(request, metadata) + pb_request = gcdc_version.UpdateVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_version.Version() + pb_resp = gcdc_version.Version.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_version(resp) + return resp + + @property + def compare_versions( + self, + ) -> Callable[[version.CompareVersionsRequest], version.CompareVersionsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CompareVersions(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_version( + self, + ) -> Callable[[gcdc_version.CreateVersionRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_version( + self, + ) -> Callable[[version.DeleteVersionRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_version(self) -> Callable[[version.GetVersionRequest], version.Version]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_versions( + self, + ) -> Callable[[version.ListVersionsRequest], version.ListVersionsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListVersions(self._session, self._host, self._interceptor) # type: ignore + + @property + def load_version( + self, + ) -> Callable[[version.LoadVersionRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._LoadVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_version( + self, + ) -> Callable[[gcdc_version.UpdateVersionRequest], gcdc_version.Version]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(VersionsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(VersionsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(VersionsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(VersionsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(VersionsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("VersionsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/services/webhooks/client.py b/google/cloud/dialogflowcx_v3/services/webhooks/client.py index 1fc4347a..324a5e91 100644 --- a/google/cloud/dialogflowcx_v3/services/webhooks/client.py +++ b/google/cloud/dialogflowcx_v3/services/webhooks/client.py @@ -56,6 +56,7 @@ from .transports.base import WebhooksTransport, DEFAULT_CLIENT_INFO from .transports.grpc import WebhooksGrpcTransport from .transports.grpc_asyncio import WebhooksGrpcAsyncIOTransport +from .transports.rest import WebhooksRestTransport class WebhooksClientMeta(type): @@ -69,6 +70,7 @@ class WebhooksClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[WebhooksTransport]] _transport_registry["grpc"] = WebhooksGrpcTransport _transport_registry["grpc_asyncio"] = WebhooksGrpcAsyncIOTransport + _transport_registry["rest"] = WebhooksRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3/services/webhooks/transports/__init__.py b/google/cloud/dialogflowcx_v3/services/webhooks/transports/__init__.py index 2beebd10..bd4c8fb7 100644 --- a/google/cloud/dialogflowcx_v3/services/webhooks/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3/services/webhooks/transports/__init__.py @@ -19,15 +19,20 @@ from .base import WebhooksTransport from .grpc import WebhooksGrpcTransport from .grpc_asyncio import WebhooksGrpcAsyncIOTransport +from .rest import WebhooksRestTransport +from .rest import WebhooksRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[WebhooksTransport]] _transport_registry["grpc"] = WebhooksGrpcTransport _transport_registry["grpc_asyncio"] = WebhooksGrpcAsyncIOTransport +_transport_registry["rest"] = WebhooksRestTransport __all__ = ( "WebhooksTransport", "WebhooksGrpcTransport", "WebhooksGrpcAsyncIOTransport", + "WebhooksRestTransport", + "WebhooksRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3/services/webhooks/transports/rest.py b/google/cloud/dialogflowcx_v3/services/webhooks/transports/rest.py new file mode 100644 index 00000000..63c09dbd --- /dev/null +++ b/google/cloud/dialogflowcx_v3/services/webhooks/transports/rest.py @@ -0,0 +1,1280 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3.types import webhook +from google.cloud.dialogflowcx_v3.types import webhook as gcdc_webhook +from google.protobuf import empty_pb2 # type: ignore + +from .base import WebhooksTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class WebhooksRestInterceptor: + """Interceptor for Webhooks. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the WebhooksRestTransport. + + .. code-block:: python + class MyCustomWebhooksInterceptor(WebhooksRestInterceptor): + def pre_create_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_webhooks(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_webhooks(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + transport = WebhooksRestTransport(interceptor=MyCustomWebhooksInterceptor()) + client = WebhooksClient(transport=transport) + + + """ + + def pre_create_webhook( + self, + request: gcdc_webhook.CreateWebhookRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_webhook.CreateWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_create_webhook( + self, response: gcdc_webhook.Webhook + ) -> gcdc_webhook.Webhook: + """Post-rpc interceptor for create_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_delete_webhook( + self, request: webhook.DeleteWebhookRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.DeleteWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def pre_get_webhook( + self, request: webhook.GetWebhookRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.GetWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_webhook(self, response: webhook.Webhook) -> webhook.Webhook: + """Post-rpc interceptor for get_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_webhooks( + self, request: webhook.ListWebhooksRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.ListWebhooksRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_webhooks + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_webhooks( + self, response: webhook.ListWebhooksResponse + ) -> webhook.ListWebhooksResponse: + """Post-rpc interceptor for list_webhooks + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_update_webhook( + self, + request: gcdc_webhook.UpdateWebhookRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_webhook.UpdateWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_update_webhook( + self, response: gcdc_webhook.Webhook + ) -> gcdc_webhook.Webhook: + """Post-rpc interceptor for update_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class WebhooksRestStub: + _session: AuthorizedSession + _host: str + _interceptor: WebhooksRestInterceptor + + +class WebhooksRestTransport(WebhooksTransport): + """REST backend transport for Webhooks. + + Service for managing + [Webhooks][google.cloud.dialogflow.cx.v3.Webhook]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[WebhooksRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or WebhooksRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateWebhook(WebhooksRestStub): + def __hash__(self): + return hash("CreateWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_webhook.CreateWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_webhook.Webhook: + r"""Call the create webhook method over HTTP. + + Args: + request (~.gcdc_webhook.CreateWebhookRequest): + The request object. The request message for + [Webhooks.CreateWebhook][google.cloud.dialogflow.cx.v3.Webhooks.CreateWebhook]. + + 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: + ~.gcdc_webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/webhooks", + "body": "webhook", + }, + ] + request, metadata = self._interceptor.pre_create_webhook(request, metadata) + pb_request = gcdc_webhook.CreateWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_webhook.Webhook() + pb_resp = gcdc_webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_webhook(resp) + return resp + + class _DeleteWebhook(WebhooksRestStub): + def __hash__(self): + return hash("DeleteWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.DeleteWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete webhook method over HTTP. + + Args: + request (~.webhook.DeleteWebhookRequest): + The request object. The request message for + [Webhooks.DeleteWebhook][google.cloud.dialogflow.cx.v3.Webhooks.DeleteWebhook]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3/{name=projects/*/locations/*/agents/*/webhooks/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_webhook(request, metadata) + pb_request = webhook.DeleteWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetWebhook(WebhooksRestStub): + def __hash__(self): + return hash("GetWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.GetWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> webhook.Webhook: + r"""Call the get webhook method over HTTP. + + Args: + request (~.webhook.GetWebhookRequest): + The request object. The request message for + [Webhooks.GetWebhook][google.cloud.dialogflow.cx.v3.Webhooks.GetWebhook]. + + 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: + ~.webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/agents/*/webhooks/*}", + }, + ] + request, metadata = self._interceptor.pre_get_webhook(request, metadata) + pb_request = webhook.GetWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = webhook.Webhook() + pb_resp = webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_webhook(resp) + return resp + + class _ListWebhooks(WebhooksRestStub): + def __hash__(self): + return hash("ListWebhooks") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.ListWebhooksRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> webhook.ListWebhooksResponse: + r"""Call the list webhooks method over HTTP. + + Args: + request (~.webhook.ListWebhooksRequest): + The request object. The request message for + [Webhooks.ListWebhooks][google.cloud.dialogflow.cx.v3.Webhooks.ListWebhooks]. + + 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: + ~.webhook.ListWebhooksResponse: + The response message for + [Webhooks.ListWebhooks][google.cloud.dialogflow.cx.v3.Webhooks.ListWebhooks]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{parent=projects/*/locations/*/agents/*}/webhooks", + }, + ] + request, metadata = self._interceptor.pre_list_webhooks(request, metadata) + pb_request = webhook.ListWebhooksRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = webhook.ListWebhooksResponse() + pb_resp = webhook.ListWebhooksResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_webhooks(resp) + return resp + + class _UpdateWebhook(WebhooksRestStub): + def __hash__(self): + return hash("UpdateWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_webhook.UpdateWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_webhook.Webhook: + r"""Call the update webhook method over HTTP. + + Args: + request (~.gcdc_webhook.UpdateWebhookRequest): + The request object. The request message for + [Webhooks.UpdateWebhook][google.cloud.dialogflow.cx.v3.Webhooks.UpdateWebhook]. + + 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: + ~.gcdc_webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3/{webhook.name=projects/*/locations/*/agents/*/webhooks/*}", + "body": "webhook", + }, + ] + request, metadata = self._interceptor.pre_update_webhook(request, metadata) + pb_request = gcdc_webhook.UpdateWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_webhook.Webhook() + pb_resp = gcdc_webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_webhook(resp) + return resp + + @property + def create_webhook( + self, + ) -> Callable[[gcdc_webhook.CreateWebhookRequest], gcdc_webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_webhook( + self, + ) -> Callable[[webhook.DeleteWebhookRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_webhook(self) -> Callable[[webhook.GetWebhookRequest], webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_webhooks( + self, + ) -> Callable[[webhook.ListWebhooksRequest], webhook.ListWebhooksResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListWebhooks(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_webhook( + self, + ) -> Callable[[gcdc_webhook.UpdateWebhookRequest], gcdc_webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(WebhooksRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(WebhooksRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("WebhooksRestTransport",) diff --git a/google/cloud/dialogflowcx_v3/types/__init__.py b/google/cloud/dialogflowcx_v3/types/__init__.py index fe4a75ea..6f53c3e8 100644 --- a/google/cloud/dialogflowcx_v3/types/__init__.py +++ b/google/cloud/dialogflowcx_v3/types/__init__.py @@ -37,6 +37,7 @@ OutputAudioConfig, SpeechWordInfo, SynthesizeSpeechConfig, + TextToSpeechSettings, VoiceSelectionParams, AudioEncoding, OutputAudioEncoding, @@ -120,6 +121,9 @@ from .fulfillment import ( Fulfillment, ) +from .gcs import ( + GcsDestination, +) from .intent import ( CreateIntentRequest, DeleteIntentRequest, @@ -280,6 +284,7 @@ "OutputAudioConfig", "SpeechWordInfo", "SynthesizeSpeechConfig", + "TextToSpeechSettings", "VoiceSelectionParams", "AudioEncoding", "OutputAudioEncoding", @@ -348,6 +353,7 @@ "UpdateFlowRequest", "ValidateFlowRequest", "Fulfillment", + "GcsDestination", "CreateIntentRequest", "DeleteIntentRequest", "GetIntentRequest", diff --git a/google/cloud/dialogflowcx_v3/types/advanced_settings.py b/google/cloud/dialogflowcx_v3/types/advanced_settings.py index 33e68ea4..973a5319 100644 --- a/google/cloud/dialogflowcx_v3/types/advanced_settings.py +++ b/google/cloud/dialogflowcx_v3/types/advanced_settings.py @@ -13,10 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore +from google.cloud.dialogflowcx_v3.types import gcs + __protobuf__ = proto.module( package="google.cloud.dialogflow.cx.v3", @@ -41,6 +45,13 @@ class AdvancedSettings(proto.Message): Hierarchy: Agent->Flow->Page->Fulfillment/Parameter. Attributes: + audio_export_gcs_destination (google.cloud.dialogflowcx_v3.types.GcsDestination): + If present, incoming audio is exported by + Dialogflow to the configured Google Cloud + Storage destination. Exposed at the following + levels: + - Agent level + - Flow level logging_settings (google.cloud.dialogflowcx_v3.types.AdvancedSettings.LoggingSettings): Settings for logging. Settings for Dialogflow History, Contact Center @@ -70,6 +81,11 @@ class LoggingSettings(proto.Message): number=3, ) + audio_export_gcs_destination: gcs.GcsDestination = proto.Field( + proto.MESSAGE, + number=2, + message=gcs.GcsDestination, + ) logging_settings: LoggingSettings = proto.Field( proto.MESSAGE, number=6, diff --git a/google/cloud/dialogflowcx_v3/types/agent.py b/google/cloud/dialogflowcx_v3/types/agent.py index d3da9422..85ee687d 100644 --- a/google/cloud/dialogflowcx_v3/types/agent.py +++ b/google/cloud/dialogflowcx_v3/types/agent.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -20,6 +22,7 @@ from google.cloud.dialogflowcx_v3.types import ( advanced_settings as gcdc_advanced_settings, ) +from google.cloud.dialogflowcx_v3.types import audio_config from google.cloud.dialogflowcx_v3.types import flow from google.protobuf import field_mask_pb2 # type: ignore @@ -139,6 +142,10 @@ class Agent(proto.Message): agent. The settings exposed at the lower level overrides the settings exposed at the higher level. + text_to_speech_settings (google.cloud.dialogflowcx_v3.types.TextToSpeechSettings): + Settings on instructing the speech + synthesizer on how to generate the output audio + content. """ name: str = proto.Field( @@ -199,6 +206,11 @@ class Agent(proto.Message): number=22, message=gcdc_advanced_settings.AdvancedSettings, ) + text_to_speech_settings: audio_config.TextToSpeechSettings = proto.Field( + proto.MESSAGE, + number=31, + message=audio_config.TextToSpeechSettings, + ) class ListAgentsRequest(proto.Message): diff --git a/google/cloud/dialogflowcx_v3/types/audio_config.py b/google/cloud/dialogflowcx_v3/types/audio_config.py index 1bd98426..64f80bb6 100644 --- a/google/cloud/dialogflowcx_v3/types/audio_config.py +++ b/google/cloud/dialogflowcx_v3/types/audio_config.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -32,6 +34,7 @@ "VoiceSelectionParams", "SynthesizeSpeechConfig", "OutputAudioConfig", + "TextToSpeechSettings", }, ) @@ -471,4 +474,25 @@ class OutputAudioConfig(proto.Message): ) +class TextToSpeechSettings(proto.Message): + r"""Settings related to speech generating. + + Attributes: + synthesize_speech_configs (MutableMapping[str, google.cloud.dialogflowcx_v3.types.SynthesizeSpeechConfig]): + Configuration of how speech should be + synthesized, mapping from language + (https://dialogflow.com/docs/reference/language) + to SynthesizeSpeechConfig. + """ + + synthesize_speech_configs: MutableMapping[ + str, "SynthesizeSpeechConfig" + ] = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message="SynthesizeSpeechConfig", + ) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflowcx_v3/types/changelog.py b/google/cloud/dialogflowcx_v3/types/changelog.py index 897655ff..b58c3ca3 100644 --- a/google/cloud/dialogflowcx_v3/types/changelog.py +++ b/google/cloud/dialogflowcx_v3/types/changelog.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/deployment.py b/google/cloud/dialogflowcx_v3/types/deployment.py index ff3999cf..e1ede854 100644 --- a/google/cloud/dialogflowcx_v3/types/deployment.py +++ b/google/cloud/dialogflowcx_v3/types/deployment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/entity_type.py b/google/cloud/dialogflowcx_v3/types/entity_type.py index 3bd32947..173465c4 100644 --- a/google/cloud/dialogflowcx_v3/types/entity_type.py +++ b/google/cloud/dialogflowcx_v3/types/entity_type.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/environment.py b/google/cloud/dialogflowcx_v3/types/environment.py index 9c898532..da073c40 100644 --- a/google/cloud/dialogflowcx_v3/types/environment.py +++ b/google/cloud/dialogflowcx_v3/types/environment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -73,10 +75,10 @@ class Environment(proto.Message): characters. If exceeded, the request is rejected. version_configs (MutableSequence[google.cloud.dialogflowcx_v3.types.Environment.VersionConfig]): - Required. A list of configurations for flow versions. You - should include version configs for all flows that are - reachable from [``Start Flow``][Agent.start_flow] in the - agent. Otherwise, an error will be returned. + A list of configurations for flow versions. You should + include version configs for all flows that are reachable + from [``Start Flow``][Agent.start_flow] in the agent. + Otherwise, an error will be returned. update_time (google.protobuf.timestamp_pb2.Timestamp): Output only. Update time of this environment. test_cases_config (google.cloud.dialogflowcx_v3.types.Environment.TestCasesConfig): diff --git a/google/cloud/dialogflowcx_v3/types/experiment.py b/google/cloud/dialogflowcx_v3/types/experiment.py index 4e818afb..ab769d3b 100644 --- a/google/cloud/dialogflowcx_v3/types/experiment.py +++ b/google/cloud/dialogflowcx_v3/types/experiment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/flow.py b/google/cloud/dialogflowcx_v3/types/flow.py index 56ea6e87..fe1eb76d 100644 --- a/google/cloud/dialogflowcx_v3/types/flow.py +++ b/google/cloud/dialogflowcx_v3/types/flow.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/fulfillment.py b/google/cloud/dialogflowcx_v3/types/fulfillment.py index f334a394..2c21e2dc 100644 --- a/google/cloud/dialogflowcx_v3/types/fulfillment.py +++ b/google/cloud/dialogflowcx_v3/types/fulfillment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/gcs.py b/google/cloud/dialogflowcx_v3/types/gcs.py new file mode 100644 index 00000000..ed80ec06 --- /dev/null +++ b/google/cloud/dialogflowcx_v3/types/gcs.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.dialogflow.cx.v3", + manifest={ + "GcsDestination", + }, +) + + +class GcsDestination(proto.Message): + r"""Google Cloud Storage location for a Dialogflow operation that + writes or exports objects (e.g. exported agent or transcripts) + outside of Dialogflow. + + Attributes: + uri (str): + Required. The Google Cloud Storage URI for + the exported objects. A URI is of the form: + gs://bucket/object-name-or-prefix + Whether a full object name, or just a prefix, + its usage depends on the Dialogflow operation. + """ + + uri: str = proto.Field( + proto.STRING, + number=1, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflowcx_v3/types/intent.py b/google/cloud/dialogflowcx_v3/types/intent.py index fa0db3c0..e195081d 100644 --- a/google/cloud/dialogflowcx_v3/types/intent.py +++ b/google/cloud/dialogflowcx_v3/types/intent.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/page.py b/google/cloud/dialogflowcx_v3/types/page.py index bf5d7f66..5a23a888 100644 --- a/google/cloud/dialogflowcx_v3/types/page.py +++ b/google/cloud/dialogflowcx_v3/types/page.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/response_message.py b/google/cloud/dialogflowcx_v3/types/response_message.py index fe91f886..6defdb94 100644 --- a/google/cloud/dialogflowcx_v3/types/response_message.py +++ b/google/cloud/dialogflowcx_v3/types/response_message.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/security_settings.py b/google/cloud/dialogflowcx_v3/types/security_settings.py index bad4e404..6fb8ffe6 100644 --- a/google/cloud/dialogflowcx_v3/types/security_settings.py +++ b/google/cloud/dialogflowcx_v3/types/security_settings.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/session.py b/google/cloud/dialogflowcx_v3/types/session.py index 9e63592b..ff760d0b 100644 --- a/google/cloud/dialogflowcx_v3/types/session.py +++ b/google/cloud/dialogflowcx_v3/types/session.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -1195,6 +1197,8 @@ class MatchIntentRequest(proto.Message): The parameters of this query. query_input (google.cloud.dialogflowcx_v3.types.QueryInput): Required. The input specification. + persist_parameter_changes (bool): + Persist session parameter changes from ``query_params``. """ session: str = proto.Field( @@ -1211,6 +1215,10 @@ class MatchIntentRequest(proto.Message): number=3, message="QueryInput", ) + persist_parameter_changes: bool = proto.Field( + proto.BOOL, + number=5, + ) class MatchIntentResponse(proto.Message): diff --git a/google/cloud/dialogflowcx_v3/types/session_entity_type.py b/google/cloud/dialogflowcx_v3/types/session_entity_type.py index b8909b91..0946c5be 100644 --- a/google/cloud/dialogflowcx_v3/types/session_entity_type.py +++ b/google/cloud/dialogflowcx_v3/types/session_entity_type.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/test_case.py b/google/cloud/dialogflowcx_v3/types/test_case.py index 5da744db..05377871 100644 --- a/google/cloud/dialogflowcx_v3/types/test_case.py +++ b/google/cloud/dialogflowcx_v3/types/test_case.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/transition_route_group.py b/google/cloud/dialogflowcx_v3/types/transition_route_group.py index 96f33960..9fe04a44 100644 --- a/google/cloud/dialogflowcx_v3/types/transition_route_group.py +++ b/google/cloud/dialogflowcx_v3/types/transition_route_group.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/validation_message.py b/google/cloud/dialogflowcx_v3/types/validation_message.py index a4eb928b..d421b9b0 100644 --- a/google/cloud/dialogflowcx_v3/types/validation_message.py +++ b/google/cloud/dialogflowcx_v3/types/validation_message.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/version.py b/google/cloud/dialogflowcx_v3/types/version.py index bfdbd290..20ae4aab 100644 --- a/google/cloud/dialogflowcx_v3/types/version.py +++ b/google/cloud/dialogflowcx_v3/types/version.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3/types/webhook.py b/google/cloud/dialogflowcx_v3/types/webhook.py index 716702d9..c3a35dd4 100644 --- a/google/cloud/dialogflowcx_v3/types/webhook.py +++ b/google/cloud/dialogflowcx_v3/types/webhook.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/__init__.py b/google/cloud/dialogflowcx_v3beta1/__init__.py index 2f3fe08b..a1833057 100644 --- a/google/cloud/dialogflowcx_v3beta1/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/__init__.py @@ -70,6 +70,7 @@ from .types.audio_config import OutputAudioConfig from .types.audio_config import SpeechWordInfo from .types.audio_config import SynthesizeSpeechConfig +from .types.audio_config import TextToSpeechSettings from .types.audio_config import VoiceSelectionParams from .types.audio_config import AudioEncoding from .types.audio_config import OutputAudioEncoding @@ -138,6 +139,7 @@ from .types.flow import UpdateFlowRequest from .types.flow import ValidateFlowRequest from .types.fulfillment import Fulfillment +from .types.gcs import GcsDestination from .types.intent import CreateIntentRequest from .types.intent import DeleteIntentRequest from .types.intent import GetIntentRequest @@ -347,6 +349,7 @@ "FulfillIntentRequest", "FulfillIntentResponse", "Fulfillment", + "GcsDestination", "GetAgentRequest", "GetAgentValidationResultRequest", "GetChangelogRequest", @@ -462,6 +465,7 @@ "TestResult", "TestRunDifference", "TextInput", + "TextToSpeechSettings", "TrainFlowRequest", "TransitionCoverage", "TransitionRoute", diff --git a/google/cloud/dialogflowcx_v3beta1/gapic_metadata.json b/google/cloud/dialogflowcx_v3beta1/gapic_metadata.json index a59ccd58..6c371762 100644 --- a/google/cloud/dialogflowcx_v3beta1/gapic_metadata.json +++ b/google/cloud/dialogflowcx_v3beta1/gapic_metadata.json @@ -106,6 +106,56 @@ ] } } + }, + "rest": { + "libraryClient": "AgentsClient", + "rpcs": { + "CreateAgent": { + "methods": [ + "create_agent" + ] + }, + "DeleteAgent": { + "methods": [ + "delete_agent" + ] + }, + "ExportAgent": { + "methods": [ + "export_agent" + ] + }, + "GetAgent": { + "methods": [ + "get_agent" + ] + }, + "GetAgentValidationResult": { + "methods": [ + "get_agent_validation_result" + ] + }, + "ListAgents": { + "methods": [ + "list_agents" + ] + }, + "RestoreAgent": { + "methods": [ + "restore_agent" + ] + }, + "UpdateAgent": { + "methods": [ + "update_agent" + ] + }, + "ValidateAgent": { + "methods": [ + "validate_agent" + ] + } + } } } }, @@ -140,6 +190,21 @@ ] } } + }, + "rest": { + "libraryClient": "ChangelogsClient", + "rpcs": { + "GetChangelog": { + "methods": [ + "get_changelog" + ] + }, + "ListChangelogs": { + "methods": [ + "list_changelogs" + ] + } + } } } }, @@ -174,6 +239,21 @@ ] } } + }, + "rest": { + "libraryClient": "DeploymentsClient", + "rpcs": { + "GetDeployment": { + "methods": [ + "get_deployment" + ] + }, + "ListDeployments": { + "methods": [ + "list_deployments" + ] + } + } } } }, @@ -238,6 +318,36 @@ ] } } + }, + "rest": { + "libraryClient": "EntityTypesClient", + "rpcs": { + "CreateEntityType": { + "methods": [ + "create_entity_type" + ] + }, + "DeleteEntityType": { + "methods": [ + "delete_entity_type" + ] + }, + "GetEntityType": { + "methods": [ + "get_entity_type" + ] + }, + "ListEntityTypes": { + "methods": [ + "list_entity_types" + ] + }, + "UpdateEntityType": { + "methods": [ + "update_entity_type" + ] + } + } } } }, @@ -342,6 +452,56 @@ ] } } + }, + "rest": { + "libraryClient": "EnvironmentsClient", + "rpcs": { + "CreateEnvironment": { + "methods": [ + "create_environment" + ] + }, + "DeleteEnvironment": { + "methods": [ + "delete_environment" + ] + }, + "DeployFlow": { + "methods": [ + "deploy_flow" + ] + }, + "GetEnvironment": { + "methods": [ + "get_environment" + ] + }, + "ListContinuousTestResults": { + "methods": [ + "list_continuous_test_results" + ] + }, + "ListEnvironments": { + "methods": [ + "list_environments" + ] + }, + "LookupEnvironmentHistory": { + "methods": [ + "lookup_environment_history" + ] + }, + "RunContinuousTest": { + "methods": [ + "run_continuous_test" + ] + }, + "UpdateEnvironment": { + "methods": [ + "update_environment" + ] + } + } } } }, @@ -426,6 +586,46 @@ ] } } + }, + "rest": { + "libraryClient": "ExperimentsClient", + "rpcs": { + "CreateExperiment": { + "methods": [ + "create_experiment" + ] + }, + "DeleteExperiment": { + "methods": [ + "delete_experiment" + ] + }, + "GetExperiment": { + "methods": [ + "get_experiment" + ] + }, + "ListExperiments": { + "methods": [ + "list_experiments" + ] + }, + "StartExperiment": { + "methods": [ + "start_experiment" + ] + }, + "StopExperiment": { + "methods": [ + "stop_experiment" + ] + }, + "UpdateExperiment": { + "methods": [ + "update_experiment" + ] + } + } } } }, @@ -540,6 +740,61 @@ ] } } + }, + "rest": { + "libraryClient": "FlowsClient", + "rpcs": { + "CreateFlow": { + "methods": [ + "create_flow" + ] + }, + "DeleteFlow": { + "methods": [ + "delete_flow" + ] + }, + "ExportFlow": { + "methods": [ + "export_flow" + ] + }, + "GetFlow": { + "methods": [ + "get_flow" + ] + }, + "GetFlowValidationResult": { + "methods": [ + "get_flow_validation_result" + ] + }, + "ImportFlow": { + "methods": [ + "import_flow" + ] + }, + "ListFlows": { + "methods": [ + "list_flows" + ] + }, + "TrainFlow": { + "methods": [ + "train_flow" + ] + }, + "UpdateFlow": { + "methods": [ + "update_flow" + ] + }, + "ValidateFlow": { + "methods": [ + "validate_flow" + ] + } + } } } }, @@ -604,13 +859,73 @@ ] } } - } - } - }, - "Pages": { - "clients": { - "grpc": { - "libraryClient": "PagesClient", + }, + "rest": { + "libraryClient": "IntentsClient", + "rpcs": { + "CreateIntent": { + "methods": [ + "create_intent" + ] + }, + "DeleteIntent": { + "methods": [ + "delete_intent" + ] + }, + "GetIntent": { + "methods": [ + "get_intent" + ] + }, + "ListIntents": { + "methods": [ + "list_intents" + ] + }, + "UpdateIntent": { + "methods": [ + "update_intent" + ] + } + } + } + } + }, + "Pages": { + "clients": { + "grpc": { + "libraryClient": "PagesClient", + "rpcs": { + "CreatePage": { + "methods": [ + "create_page" + ] + }, + "DeletePage": { + "methods": [ + "delete_page" + ] + }, + "GetPage": { + "methods": [ + "get_page" + ] + }, + "ListPages": { + "methods": [ + "list_pages" + ] + }, + "UpdatePage": { + "methods": [ + "update_page" + ] + } + } + }, + "grpc-async": { + "libraryClient": "PagesAsyncClient", "rpcs": { "CreatePage": { "methods": [ @@ -639,8 +954,8 @@ } } }, - "grpc-async": { - "libraryClient": "PagesAsyncClient", + "rest": { + "libraryClient": "PagesClient", "rpcs": { "CreatePage": { "methods": [ @@ -732,6 +1047,36 @@ ] } } + }, + "rest": { + "libraryClient": "SecuritySettingsServiceClient", + "rpcs": { + "CreateSecuritySettings": { + "methods": [ + "create_security_settings" + ] + }, + "DeleteSecuritySettings": { + "methods": [ + "delete_security_settings" + ] + }, + "GetSecuritySettings": { + "methods": [ + "get_security_settings" + ] + }, + "ListSecuritySettings": { + "methods": [ + "list_security_settings" + ] + }, + "UpdateSecuritySettings": { + "methods": [ + "update_security_settings" + ] + } + } } } }, @@ -796,6 +1141,36 @@ ] } } + }, + "rest": { + "libraryClient": "SessionEntityTypesClient", + "rpcs": { + "CreateSessionEntityType": { + "methods": [ + "create_session_entity_type" + ] + }, + "DeleteSessionEntityType": { + "methods": [ + "delete_session_entity_type" + ] + }, + "GetSessionEntityType": { + "methods": [ + "get_session_entity_type" + ] + }, + "ListSessionEntityTypes": { + "methods": [ + "list_session_entity_types" + ] + }, + "UpdateSessionEntityType": { + "methods": [ + "update_session_entity_type" + ] + } + } } } }, @@ -850,6 +1225,31 @@ ] } } + }, + "rest": { + "libraryClient": "SessionsClient", + "rpcs": { + "DetectIntent": { + "methods": [ + "detect_intent" + ] + }, + "FulfillIntent": { + "methods": [ + "fulfill_intent" + ] + }, + "MatchIntent": { + "methods": [ + "match_intent" + ] + }, + "StreamingDetectIntent": { + "methods": [ + "streaming_detect_intent" + ] + } + } } } }, @@ -984,6 +1384,71 @@ ] } } + }, + "rest": { + "libraryClient": "TestCasesClient", + "rpcs": { + "BatchDeleteTestCases": { + "methods": [ + "batch_delete_test_cases" + ] + }, + "BatchRunTestCases": { + "methods": [ + "batch_run_test_cases" + ] + }, + "CalculateCoverage": { + "methods": [ + "calculate_coverage" + ] + }, + "CreateTestCase": { + "methods": [ + "create_test_case" + ] + }, + "ExportTestCases": { + "methods": [ + "export_test_cases" + ] + }, + "GetTestCase": { + "methods": [ + "get_test_case" + ] + }, + "GetTestCaseResult": { + "methods": [ + "get_test_case_result" + ] + }, + "ImportTestCases": { + "methods": [ + "import_test_cases" + ] + }, + "ListTestCaseResults": { + "methods": [ + "list_test_case_results" + ] + }, + "ListTestCases": { + "methods": [ + "list_test_cases" + ] + }, + "RunTestCase": { + "methods": [ + "run_test_case" + ] + }, + "UpdateTestCase": { + "methods": [ + "update_test_case" + ] + } + } } } }, @@ -1048,6 +1513,36 @@ ] } } + }, + "rest": { + "libraryClient": "TransitionRouteGroupsClient", + "rpcs": { + "CreateTransitionRouteGroup": { + "methods": [ + "create_transition_route_group" + ] + }, + "DeleteTransitionRouteGroup": { + "methods": [ + "delete_transition_route_group" + ] + }, + "GetTransitionRouteGroup": { + "methods": [ + "get_transition_route_group" + ] + }, + "ListTransitionRouteGroups": { + "methods": [ + "list_transition_route_groups" + ] + }, + "UpdateTransitionRouteGroup": { + "methods": [ + "update_transition_route_group" + ] + } + } } } }, @@ -1132,6 +1627,46 @@ ] } } + }, + "rest": { + "libraryClient": "VersionsClient", + "rpcs": { + "CompareVersions": { + "methods": [ + "compare_versions" + ] + }, + "CreateVersion": { + "methods": [ + "create_version" + ] + }, + "DeleteVersion": { + "methods": [ + "delete_version" + ] + }, + "GetVersion": { + "methods": [ + "get_version" + ] + }, + "ListVersions": { + "methods": [ + "list_versions" + ] + }, + "LoadVersion": { + "methods": [ + "load_version" + ] + }, + "UpdateVersion": { + "methods": [ + "update_version" + ] + } + } } } }, @@ -1196,6 +1731,36 @@ ] } } + }, + "rest": { + "libraryClient": "WebhooksClient", + "rpcs": { + "CreateWebhook": { + "methods": [ + "create_webhook" + ] + }, + "DeleteWebhook": { + "methods": [ + "delete_webhook" + ] + }, + "GetWebhook": { + "methods": [ + "get_webhook" + ] + }, + "ListWebhooks": { + "methods": [ + "list_webhooks" + ] + }, + "UpdateWebhook": { + "methods": [ + "update_webhook" + ] + } + } } } } diff --git a/google/cloud/dialogflowcx_v3beta1/services/agents/async_client.py b/google/cloud/dialogflowcx_v3beta1/services/agents/async_client.py index a94a1794..c7dc989f 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/agents/async_client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/agents/async_client.py @@ -48,6 +48,7 @@ from google.cloud.dialogflowcx_v3beta1.types import advanced_settings from google.cloud.dialogflowcx_v3beta1.types import agent from google.cloud.dialogflowcx_v3beta1.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3beta1.types import audio_config from google.cloud.dialogflowcx_v3beta1.types import flow from google.cloud.location import locations_pb2 # type: ignore from google.longrunning import operations_pb2 diff --git a/google/cloud/dialogflowcx_v3beta1/services/agents/client.py b/google/cloud/dialogflowcx_v3beta1/services/agents/client.py index c20d20e8..40d9deaa 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/agents/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/agents/client.py @@ -52,6 +52,7 @@ from google.cloud.dialogflowcx_v3beta1.types import advanced_settings from google.cloud.dialogflowcx_v3beta1.types import agent from google.cloud.dialogflowcx_v3beta1.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3beta1.types import audio_config from google.cloud.dialogflowcx_v3beta1.types import flow from google.cloud.location import locations_pb2 # type: ignore from google.longrunning import operations_pb2 @@ -61,6 +62,7 @@ from .transports.base import AgentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import AgentsGrpcTransport from .transports.grpc_asyncio import AgentsGrpcAsyncIOTransport +from .transports.rest import AgentsRestTransport class AgentsClientMeta(type): @@ -74,6 +76,7 @@ class AgentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[AgentsTransport]] _transport_registry["grpc"] = AgentsGrpcTransport _transport_registry["grpc_asyncio"] = AgentsGrpcAsyncIOTransport + _transport_registry["rest"] = AgentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/agents/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/agents/transports/__init__.py index 703a3046..a94f62c7 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/agents/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/agents/transports/__init__.py @@ -19,15 +19,20 @@ from .base import AgentsTransport from .grpc import AgentsGrpcTransport from .grpc_asyncio import AgentsGrpcAsyncIOTransport +from .rest import AgentsRestTransport +from .rest import AgentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[AgentsTransport]] _transport_registry["grpc"] = AgentsGrpcTransport _transport_registry["grpc_asyncio"] = AgentsGrpcAsyncIOTransport +_transport_registry["rest"] = AgentsRestTransport __all__ = ( "AgentsTransport", "AgentsGrpcTransport", "AgentsGrpcAsyncIOTransport", + "AgentsRestTransport", + "AgentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/agents/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/agents/transports/rest.py new file mode 100644 index 00000000..a4aa2fe1 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/agents/transports/rest.py @@ -0,0 +1,1894 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import agent +from google.cloud.dialogflowcx_v3beta1.types import agent as gcdc_agent +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import AgentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class AgentsRestInterceptor: + """Interceptor for Agents. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the AgentsRestTransport. + + .. code-block:: python + class MyCustomAgentsInterceptor(AgentsRestInterceptor): + def pre_create_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_export_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_agent_validation_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_agent_validation_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_agents(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_agents(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_restore_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_restore_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_agent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_agent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_agent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = AgentsRestTransport(interceptor=MyCustomAgentsInterceptor()) + client = AgentsClient(transport=transport) + + + """ + + def pre_create_agent( + self, + request: gcdc_agent.CreateAgentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_agent.CreateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_create_agent(self, response: gcdc_agent.Agent) -> gcdc_agent.Agent: + """Post-rpc interceptor for create_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_delete_agent( + self, request: agent.DeleteAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.DeleteAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def pre_export_agent( + self, request: agent.ExportAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ExportAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_export_agent( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_agent( + self, request: agent.GetAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.GetAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_agent(self, response: agent.Agent) -> agent.Agent: + """Post-rpc interceptor for get_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_agent_validation_result( + self, + request: agent.GetAgentValidationResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[agent.GetAgentValidationResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_agent_validation_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_agent_validation_result( + self, response: agent.AgentValidationResult + ) -> agent.AgentValidationResult: + """Post-rpc interceptor for get_agent_validation_result + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_agents( + self, request: agent.ListAgentsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ListAgentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_agents + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_agents( + self, response: agent.ListAgentsResponse + ) -> agent.ListAgentsResponse: + """Post-rpc interceptor for list_agents + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_restore_agent( + self, request: agent.RestoreAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.RestoreAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for restore_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_restore_agent( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for restore_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_update_agent( + self, + request: gcdc_agent.UpdateAgentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_agent.UpdateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_update_agent(self, response: gcdc_agent.Agent) -> gcdc_agent.Agent: + """Post-rpc interceptor for update_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_validate_agent( + self, request: agent.ValidateAgentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[agent.ValidateAgentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_agent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_validate_agent( + self, response: agent.AgentValidationResult + ) -> agent.AgentValidationResult: + """Post-rpc interceptor for validate_agent + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Agents server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Agents server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class AgentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: AgentsRestInterceptor + + +class AgentsRestTransport(AgentsTransport): + """REST backend transport for Agents. + + Service for managing + [Agents][google.cloud.dialogflow.cx.v3beta1.Agent]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[AgentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or AgentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3beta1", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateAgent(AgentsRestStub): + def __hash__(self): + return hash("CreateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_agent.CreateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_agent.Agent: + r"""Call the create agent method over HTTP. + + Args: + request (~.gcdc_agent.CreateAgentRequest): + The request object. The request message for + [Agents.CreateAgent][google.cloud.dialogflow.cx.v3beta1.Agents.CreateAgent]. + + 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: + ~.gcdc_agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3beta1.Intent], + [Entity + Types][google.cloud.dialogflow.cx.v3beta1.EntityType], + [Flows][google.cloud.dialogflow.cx.v3beta1.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3beta1.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3beta1.Webhook], + and so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*}/agents", + "body": "agent", + }, + ] + request, metadata = self._interceptor.pre_create_agent(request, metadata) + pb_request = gcdc_agent.CreateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_agent.Agent() + pb_resp = gcdc_agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_agent(resp) + return resp + + class _DeleteAgent(AgentsRestStub): + def __hash__(self): + return hash("DeleteAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.DeleteAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete agent method over HTTP. + + Args: + request (~.agent.DeleteAgentRequest): + The request object. The request message for + [Agents.DeleteAgent][google.cloud.dialogflow.cx.v3beta1.Agents.DeleteAgent]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_agent(request, metadata) + pb_request = agent.DeleteAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _ExportAgent(AgentsRestStub): + def __hash__(self): + return hash("ExportAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ExportAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export agent method over HTTP. + + Args: + request (~.agent.ExportAgentRequest): + The request object. The request message for + [Agents.ExportAgent][google.cloud.dialogflow.cx.v3beta1.Agents.ExportAgent]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*}:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_agent(request, metadata) + pb_request = agent.ExportAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_agent(resp) + return resp + + class _GetAgent(AgentsRestStub): + def __hash__(self): + return hash("GetAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.GetAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.Agent: + r"""Call the get agent method over HTTP. + + Args: + request (~.agent.GetAgentRequest): + The request object. The request message for + [Agents.GetAgent][google.cloud.dialogflow.cx.v3beta1.Agents.GetAgent]. + + 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: + ~.agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3beta1.Intent], + [Entity + Types][google.cloud.dialogflow.cx.v3beta1.EntityType], + [Flows][google.cloud.dialogflow.cx.v3beta1.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3beta1.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3beta1.Webhook], + and so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*}", + }, + ] + request, metadata = self._interceptor.pre_get_agent(request, metadata) + pb_request = agent.GetAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.Agent() + pb_resp = agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_agent(resp) + return resp + + class _GetAgentValidationResult(AgentsRestStub): + def __hash__(self): + return hash("GetAgentValidationResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.GetAgentValidationResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.AgentValidationResult: + r"""Call the get agent validation + result method over HTTP. + + Args: + request (~.agent.GetAgentValidationResultRequest): + The request object. The request message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3beta1.Agents.GetAgentValidationResult]. + + 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: + ~.agent.AgentValidationResult: + The response message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3beta1.Agents.GetAgentValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/validationResult}", + }, + ] + request, metadata = self._interceptor.pre_get_agent_validation_result( + request, metadata + ) + pb_request = agent.GetAgentValidationResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.AgentValidationResult() + pb_resp = agent.AgentValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_agent_validation_result(resp) + return resp + + class _ListAgents(AgentsRestStub): + def __hash__(self): + return hash("ListAgents") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ListAgentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.ListAgentsResponse: + r"""Call the list agents method over HTTP. + + Args: + request (~.agent.ListAgentsRequest): + The request object. The request message for + [Agents.ListAgents][google.cloud.dialogflow.cx.v3beta1.Agents.ListAgents]. + + 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: + ~.agent.ListAgentsResponse: + The response message for + [Agents.ListAgents][google.cloud.dialogflow.cx.v3beta1.Agents.ListAgents]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*}/agents", + }, + ] + request, metadata = self._interceptor.pre_list_agents(request, metadata) + pb_request = agent.ListAgentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.ListAgentsResponse() + pb_resp = agent.ListAgentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_agents(resp) + return resp + + class _RestoreAgent(AgentsRestStub): + def __hash__(self): + return hash("RestoreAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.RestoreAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the restore agent method over HTTP. + + Args: + request (~.agent.RestoreAgentRequest): + The request object. The request message for + [Agents.RestoreAgent][google.cloud.dialogflow.cx.v3beta1.Agents.RestoreAgent]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*}:restore", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_restore_agent(request, metadata) + pb_request = agent.RestoreAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_restore_agent(resp) + return resp + + class _UpdateAgent(AgentsRestStub): + def __hash__(self): + return hash("UpdateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_agent.UpdateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_agent.Agent: + r"""Call the update agent method over HTTP. + + Args: + request (~.gcdc_agent.UpdateAgentRequest): + The request object. The request message for + [Agents.UpdateAgent][google.cloud.dialogflow.cx.v3beta1.Agents.UpdateAgent]. + + 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: + ~.gcdc_agent.Agent: + Agents are best described as Natural Language + Understanding (NLU) modules that transform user requests + into actionable data. You can include agents in your + app, product, or service to determine user intent and + respond to the user in a natural way. + + After you create an agent, you can add + [Intents][google.cloud.dialogflow.cx.v3beta1.Intent], + [Entity + Types][google.cloud.dialogflow.cx.v3beta1.EntityType], + [Flows][google.cloud.dialogflow.cx.v3beta1.Flow], + [Fulfillments][google.cloud.dialogflow.cx.v3beta1.Fulfillment], + [Webhooks][google.cloud.dialogflow.cx.v3beta1.Webhook], + and so on to manage the conversation flows.. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{agent.name=projects/*/locations/*/agents/*}", + "body": "agent", + }, + ] + request, metadata = self._interceptor.pre_update_agent(request, metadata) + pb_request = gcdc_agent.UpdateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_agent.Agent() + pb_resp = gcdc_agent.Agent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_agent(resp) + return resp + + class _ValidateAgent(AgentsRestStub): + def __hash__(self): + return hash("ValidateAgent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: agent.ValidateAgentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> agent.AgentValidationResult: + r"""Call the validate agent method over HTTP. + + Args: + request (~.agent.ValidateAgentRequest): + The request object. The request message for + [Agents.ValidateAgent][google.cloud.dialogflow.cx.v3beta1.Agents.ValidateAgent]. + + 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: + ~.agent.AgentValidationResult: + The response message for + [Agents.GetAgentValidationResult][google.cloud.dialogflow.cx.v3beta1.Agents.GetAgentValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*}:validate", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_agent(request, metadata) + pb_request = agent.ValidateAgentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = agent.AgentValidationResult() + pb_resp = agent.AgentValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_agent(resp) + return resp + + @property + def create_agent( + self, + ) -> Callable[[gcdc_agent.CreateAgentRequest], gcdc_agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_agent(self) -> Callable[[agent.DeleteAgentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_agent( + self, + ) -> Callable[[agent.ExportAgentRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_agent(self) -> Callable[[agent.GetAgentRequest], agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_agent_validation_result( + self, + ) -> Callable[[agent.GetAgentValidationResultRequest], agent.AgentValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetAgentValidationResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_agents( + self, + ) -> Callable[[agent.ListAgentsRequest], agent.ListAgentsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListAgents(self._session, self._host, self._interceptor) # type: ignore + + @property + def restore_agent( + self, + ) -> Callable[[agent.RestoreAgentRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RestoreAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_agent( + self, + ) -> Callable[[gcdc_agent.UpdateAgentRequest], gcdc_agent.Agent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_agent( + self, + ) -> Callable[[agent.ValidateAgentRequest], agent.AgentValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ValidateAgent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(AgentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(AgentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(AgentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(AgentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(AgentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("AgentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/changelogs/client.py b/google/cloud/dialogflowcx_v3beta1/services/changelogs/client.py index 250501d1..e7d7ba69 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/changelogs/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/changelogs/client.py @@ -54,6 +54,7 @@ from .transports.base import ChangelogsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ChangelogsGrpcTransport from .transports.grpc_asyncio import ChangelogsGrpcAsyncIOTransport +from .transports.rest import ChangelogsRestTransport class ChangelogsClientMeta(type): @@ -67,6 +68,7 @@ class ChangelogsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[ChangelogsTransport]] _transport_registry["grpc"] = ChangelogsGrpcTransport _transport_registry["grpc_asyncio"] = ChangelogsGrpcAsyncIOTransport + _transport_registry["rest"] = ChangelogsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/__init__.py index 34aa1f62..49d7fb49 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/__init__.py @@ -19,15 +19,20 @@ from .base import ChangelogsTransport from .grpc import ChangelogsGrpcTransport from .grpc_asyncio import ChangelogsGrpcAsyncIOTransport +from .rest import ChangelogsRestTransport +from .rest import ChangelogsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[ChangelogsTransport]] _transport_registry["grpc"] = ChangelogsGrpcTransport _transport_registry["grpc_asyncio"] = ChangelogsGrpcAsyncIOTransport +_transport_registry["rest"] = ChangelogsRestTransport __all__ = ( "ChangelogsTransport", "ChangelogsGrpcTransport", "ChangelogsGrpcAsyncIOTransport", + "ChangelogsRestTransport", + "ChangelogsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/rest.py new file mode 100644 index 00000000..693a5981 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/changelogs/transports/rest.py @@ -0,0 +1,895 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import changelog + +from .base import ChangelogsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class ChangelogsRestInterceptor: + """Interceptor for Changelogs. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ChangelogsRestTransport. + + .. code-block:: python + class MyCustomChangelogsInterceptor(ChangelogsRestInterceptor): + def pre_get_changelog(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_changelog(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_changelogs(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_changelogs(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ChangelogsRestTransport(interceptor=MyCustomChangelogsInterceptor()) + client = ChangelogsClient(transport=transport) + + + """ + + def pre_get_changelog( + self, + request: changelog.GetChangelogRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[changelog.GetChangelogRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_changelog + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_changelog(self, response: changelog.Changelog) -> changelog.Changelog: + """Post-rpc interceptor for get_changelog + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_changelogs( + self, + request: changelog.ListChangelogsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[changelog.ListChangelogsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_changelogs + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_changelogs( + self, response: changelog.ListChangelogsResponse + ) -> changelog.ListChangelogsResponse: + """Post-rpc interceptor for list_changelogs + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Changelogs server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Changelogs server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class ChangelogsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ChangelogsRestInterceptor + + +class ChangelogsRestTransport(ChangelogsTransport): + """REST backend transport for Changelogs. + + Service for managing + [Changelogs][google.cloud.dialogflow.cx.v3beta1.Changelog]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ChangelogsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ChangelogsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GetChangelog(ChangelogsRestStub): + def __hash__(self): + return hash("GetChangelog") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: changelog.GetChangelogRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> changelog.Changelog: + r"""Call the get changelog method over HTTP. + + Args: + request (~.changelog.GetChangelogRequest): + The request object. The request message for + [Changelogs.GetChangelog][google.cloud.dialogflow.cx.v3beta1.Changelogs.GetChangelog]. + + 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: + ~.changelog.Changelog: + Changelogs represents a change made + to a given agent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/changelogs/*}", + }, + ] + request, metadata = self._interceptor.pre_get_changelog(request, metadata) + pb_request = changelog.GetChangelogRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = changelog.Changelog() + pb_resp = changelog.Changelog.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_changelog(resp) + return resp + + class _ListChangelogs(ChangelogsRestStub): + def __hash__(self): + return hash("ListChangelogs") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: changelog.ListChangelogsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> changelog.ListChangelogsResponse: + r"""Call the list changelogs method over HTTP. + + Args: + request (~.changelog.ListChangelogsRequest): + The request object. The request message for + [Changelogs.ListChangelogs][google.cloud.dialogflow.cx.v3beta1.Changelogs.ListChangelogs]. + + 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: + ~.changelog.ListChangelogsResponse: + The response message for + [Changelogs.ListChangelogs][google.cloud.dialogflow.cx.v3beta1.Changelogs.ListChangelogs]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/changelogs", + }, + ] + request, metadata = self._interceptor.pre_list_changelogs(request, metadata) + pb_request = changelog.ListChangelogsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = changelog.ListChangelogsResponse() + pb_resp = changelog.ListChangelogsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_changelogs(resp) + return resp + + @property + def get_changelog( + self, + ) -> Callable[[changelog.GetChangelogRequest], changelog.Changelog]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetChangelog(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_changelogs( + self, + ) -> Callable[[changelog.ListChangelogsRequest], changelog.ListChangelogsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListChangelogs(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(ChangelogsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(ChangelogsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(ChangelogsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ChangelogsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/deployments/client.py b/google/cloud/dialogflowcx_v3beta1/services/deployments/client.py index 4dec80e9..e477cd9c 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/deployments/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/deployments/client.py @@ -54,6 +54,7 @@ from .transports.base import DeploymentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import DeploymentsGrpcTransport from .transports.grpc_asyncio import DeploymentsGrpcAsyncIOTransport +from .transports.rest import DeploymentsRestTransport class DeploymentsClientMeta(type): @@ -67,6 +68,7 @@ class DeploymentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[DeploymentsTransport]] _transport_registry["grpc"] = DeploymentsGrpcTransport _transport_registry["grpc_asyncio"] = DeploymentsGrpcAsyncIOTransport + _transport_registry["rest"] = DeploymentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/__init__.py index f902c685..c128af3e 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import DeploymentsTransport from .grpc import DeploymentsGrpcTransport from .grpc_asyncio import DeploymentsGrpcAsyncIOTransport +from .rest import DeploymentsRestTransport +from .rest import DeploymentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[DeploymentsTransport]] _transport_registry["grpc"] = DeploymentsGrpcTransport _transport_registry["grpc_asyncio"] = DeploymentsGrpcAsyncIOTransport +_transport_registry["rest"] = DeploymentsRestTransport __all__ = ( "DeploymentsTransport", "DeploymentsGrpcTransport", "DeploymentsGrpcAsyncIOTransport", + "DeploymentsRestTransport", + "DeploymentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/rest.py new file mode 100644 index 00000000..7e232356 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/deployments/transports/rest.py @@ -0,0 +1,906 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import deployment + +from .base import DeploymentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class DeploymentsRestInterceptor: + """Interceptor for Deployments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the DeploymentsRestTransport. + + .. code-block:: python + class MyCustomDeploymentsInterceptor(DeploymentsRestInterceptor): + def pre_get_deployment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_deployment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_deployments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_deployments(self, response): + logging.log(f"Received response: {response}") + return response + + transport = DeploymentsRestTransport(interceptor=MyCustomDeploymentsInterceptor()) + client = DeploymentsClient(transport=transport) + + + """ + + def pre_get_deployment( + self, + request: deployment.GetDeploymentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[deployment.GetDeploymentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_deployment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_deployment( + self, response: deployment.Deployment + ) -> deployment.Deployment: + """Post-rpc interceptor for get_deployment + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_deployments( + self, + request: deployment.ListDeploymentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[deployment.ListDeploymentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_deployments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_deployments( + self, response: deployment.ListDeploymentsResponse + ) -> deployment.ListDeploymentsResponse: + """Post-rpc interceptor for list_deployments + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Deployments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Deployments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class DeploymentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: DeploymentsRestInterceptor + + +class DeploymentsRestTransport(DeploymentsTransport): + """REST backend transport for Deployments. + + Service for managing + [Deployments][google.cloud.dialogflow.cx.v3beta1.Deployment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[DeploymentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or DeploymentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GetDeployment(DeploymentsRestStub): + def __hash__(self): + return hash("GetDeployment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: deployment.GetDeploymentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> deployment.Deployment: + r"""Call the get deployment method over HTTP. + + Args: + request (~.deployment.GetDeploymentRequest): + The request object. The request message for + [Deployments.GetDeployment][google.cloud.dialogflow.cx.v3beta1.Deployments.GetDeployment]. + + 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: + ~.deployment.Deployment: + Represents an deployment in an + environment. A deployment happens when a + flow version configured to be active in + the environment. You can configure + running pre-deployment steps, e.g. + running validation test cases, + experiment auto-rollout, etc. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/deployments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_deployment(request, metadata) + pb_request = deployment.GetDeploymentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = deployment.Deployment() + pb_resp = deployment.Deployment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_deployment(resp) + return resp + + class _ListDeployments(DeploymentsRestStub): + def __hash__(self): + return hash("ListDeployments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: deployment.ListDeploymentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> deployment.ListDeploymentsResponse: + r"""Call the list deployments method over HTTP. + + Args: + request (~.deployment.ListDeploymentsRequest): + The request object. The request message for + [Deployments.ListDeployments][google.cloud.dialogflow.cx.v3beta1.Deployments.ListDeployments]. + + 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: + ~.deployment.ListDeploymentsResponse: + The response message for + [Deployments.ListDeployments][google.cloud.dialogflow.cx.v3beta1.Deployments.ListDeployments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/deployments", + }, + ] + request, metadata = self._interceptor.pre_list_deployments( + request, metadata + ) + pb_request = deployment.ListDeploymentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = deployment.ListDeploymentsResponse() + pb_resp = deployment.ListDeploymentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_deployments(resp) + return resp + + @property + def get_deployment( + self, + ) -> Callable[[deployment.GetDeploymentRequest], deployment.Deployment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetDeployment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_deployments( + self, + ) -> Callable[ + [deployment.ListDeploymentsRequest], deployment.ListDeploymentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListDeployments(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(DeploymentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(DeploymentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(DeploymentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("DeploymentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/entity_types/client.py b/google/cloud/dialogflowcx_v3beta1/services/entity_types/client.py index d212601c..f6e3bdb6 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/entity_types/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/entity_types/client.py @@ -55,6 +55,7 @@ from .transports.base import EntityTypesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import EntityTypesGrpcTransport from .transports.grpc_asyncio import EntityTypesGrpcAsyncIOTransport +from .transports.rest import EntityTypesRestTransport class EntityTypesClientMeta(type): @@ -68,6 +69,7 @@ class EntityTypesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[EntityTypesTransport]] _transport_registry["grpc"] = EntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = EntityTypesGrpcAsyncIOTransport + _transport_registry["rest"] = EntityTypesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/__init__.py index 2a22d664..0220987e 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/__init__.py @@ -19,15 +19,20 @@ from .base import EntityTypesTransport from .grpc import EntityTypesGrpcTransport from .grpc_asyncio import EntityTypesGrpcAsyncIOTransport +from .rest import EntityTypesRestTransport +from .rest import EntityTypesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[EntityTypesTransport]] _transport_registry["grpc"] = EntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = EntityTypesGrpcAsyncIOTransport +_transport_registry["rest"] = EntityTypesRestTransport __all__ = ( "EntityTypesTransport", "EntityTypesGrpcTransport", "EntityTypesGrpcAsyncIOTransport", + "EntityTypesRestTransport", + "EntityTypesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/rest.py new file mode 100644 index 00000000..147cc1d6 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/entity_types/transports/rest.py @@ -0,0 +1,1388 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import entity_type +from google.cloud.dialogflowcx_v3beta1.types import entity_type as gcdc_entity_type +from google.protobuf import empty_pb2 # type: ignore + +from .base import EntityTypesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class EntityTypesRestInterceptor: + """Interceptor for EntityTypes. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the EntityTypesRestTransport. + + .. code-block:: python + class MyCustomEntityTypesInterceptor(EntityTypesRestInterceptor): + def pre_create_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_entity_types(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_entity_types(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + transport = EntityTypesRestTransport(interceptor=MyCustomEntityTypesInterceptor()) + client = EntityTypesClient(transport=transport) + + + """ + + def pre_create_entity_type( + self, + request: gcdc_entity_type.CreateEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_entity_type.CreateEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_create_entity_type( + self, response: gcdc_entity_type.EntityType + ) -> gcdc_entity_type.EntityType: + """Post-rpc interceptor for create_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_delete_entity_type( + self, + request: entity_type.DeleteEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.DeleteEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def pre_get_entity_type( + self, + request: entity_type.GetEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.GetEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_entity_type( + self, response: entity_type.EntityType + ) -> entity_type.EntityType: + """Post-rpc interceptor for get_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_entity_types( + self, + request: entity_type.ListEntityTypesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[entity_type.ListEntityTypesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_entity_types + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_entity_types( + self, response: entity_type.ListEntityTypesResponse + ) -> entity_type.ListEntityTypesResponse: + """Post-rpc interceptor for list_entity_types + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_update_entity_type( + self, + request: gcdc_entity_type.UpdateEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_entity_type.UpdateEntityTypeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_update_entity_type( + self, response: gcdc_entity_type.EntityType + ) -> gcdc_entity_type.EntityType: + """Post-rpc interceptor for update_entity_type + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the EntityTypes server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the EntityTypes server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class EntityTypesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: EntityTypesRestInterceptor + + +class EntityTypesRestTransport(EntityTypesTransport): + """REST backend transport for EntityTypes. + + Service for managing + [EntityTypes][google.cloud.dialogflow.cx.v3beta1.EntityType]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[EntityTypesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or EntityTypesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("CreateEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_entity_type.CreateEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_entity_type.EntityType: + r"""Call the create entity type method over HTTP. + + Args: + request (~.gcdc_entity_type.CreateEntityTypeRequest): + The request object. The request message for + [EntityTypes.CreateEntityType][google.cloud.dialogflow.cx.v3beta1.EntityTypes.CreateEntityType]. + + 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: + ~.gcdc_entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/entityTypes", + "body": "entity_type", + }, + ] + request, metadata = self._interceptor.pre_create_entity_type( + request, metadata + ) + pb_request = gcdc_entity_type.CreateEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_entity_type.EntityType() + pb_resp = gcdc_entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_entity_type(resp) + return resp + + class _DeleteEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("DeleteEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.DeleteEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete entity type method over HTTP. + + Args: + request (~.entity_type.DeleteEntityTypeRequest): + The request object. The request message for + [EntityTypes.DeleteEntityType][google.cloud.dialogflow.cx.v3beta1.EntityTypes.DeleteEntityType]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_entity_type( + request, metadata + ) + pb_request = entity_type.DeleteEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("GetEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.GetEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> entity_type.EntityType: + r"""Call the get entity type method over HTTP. + + Args: + request (~.entity_type.GetEntityTypeRequest): + The request object. The request message for + [EntityTypes.GetEntityType][google.cloud.dialogflow.cx.v3beta1.EntityTypes.GetEntityType]. + + 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: + ~.entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_get_entity_type(request, metadata) + pb_request = entity_type.GetEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = entity_type.EntityType() + pb_resp = entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_entity_type(resp) + return resp + + class _ListEntityTypes(EntityTypesRestStub): + def __hash__(self): + return hash("ListEntityTypes") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: entity_type.ListEntityTypesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> entity_type.ListEntityTypesResponse: + r"""Call the list entity types method over HTTP. + + Args: + request (~.entity_type.ListEntityTypesRequest): + The request object. The request message for + [EntityTypes.ListEntityTypes][google.cloud.dialogflow.cx.v3beta1.EntityTypes.ListEntityTypes]. + + 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: + ~.entity_type.ListEntityTypesResponse: + The response message for + [EntityTypes.ListEntityTypes][google.cloud.dialogflow.cx.v3beta1.EntityTypes.ListEntityTypes]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/entityTypes", + }, + ] + request, metadata = self._interceptor.pre_list_entity_types( + request, metadata + ) + pb_request = entity_type.ListEntityTypesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = entity_type.ListEntityTypesResponse() + pb_resp = entity_type.ListEntityTypesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_entity_types(resp) + return resp + + class _UpdateEntityType(EntityTypesRestStub): + def __hash__(self): + return hash("UpdateEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_entity_type.UpdateEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_entity_type.EntityType: + r"""Call the update entity type method over HTTP. + + Args: + request (~.gcdc_entity_type.UpdateEntityTypeRequest): + The request object. The request message for + [EntityTypes.UpdateEntityType][google.cloud.dialogflow.cx.v3beta1.EntityTypes.UpdateEntityType]. + + 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: + ~.gcdc_entity_type.EntityType: + Entities are extracted from user input and represent + parameters that are meaningful to your application. For + example, a date range, a proper name such as a + geographic location or landmark, and so on. Entities + represent actionable data for your application. + + When you define an entity, you can also include synonyms + that all map to that entity. For example, "soft drink", + "soda", "pop", and so on. + + There are three types of entities: + + - **System** - entities that are defined by the + Dialogflow API for common data types such as date, + time, currency, and so on. A system entity is + represented by the ``EntityType`` type. + + - **Custom** - entities that are defined by you that + represent actionable data that is meaningful to your + application. For example, you could define a + ``pizza.sauce`` entity for red or white pizza sauce, + a ``pizza.cheese`` entity for the different types of + cheese on a pizza, a ``pizza.topping`` entity for + different toppings, and so on. A custom entity is + represented by the ``EntityType`` type. + + - **User** - entities that are built for an individual + user such as favorites, preferences, playlists, and + so on. A user entity is represented by the + [SessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityType] + type. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{entity_type.name=projects/*/locations/*/agents/*/entityTypes/*}", + "body": "entity_type", + }, + ] + request, metadata = self._interceptor.pre_update_entity_type( + request, metadata + ) + pb_request = gcdc_entity_type.UpdateEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_entity_type.EntityType() + pb_resp = gcdc_entity_type.EntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_entity_type(resp) + return resp + + @property + def create_entity_type( + self, + ) -> Callable[ + [gcdc_entity_type.CreateEntityTypeRequest], gcdc_entity_type.EntityType + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_entity_type( + self, + ) -> Callable[[entity_type.DeleteEntityTypeRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_entity_type( + self, + ) -> Callable[[entity_type.GetEntityTypeRequest], entity_type.EntityType]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_entity_types( + self, + ) -> Callable[ + [entity_type.ListEntityTypesRequest], entity_type.ListEntityTypesResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListEntityTypes(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_entity_type( + self, + ) -> Callable[ + [gcdc_entity_type.UpdateEntityTypeRequest], gcdc_entity_type.EntityType + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(EntityTypesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(EntityTypesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(EntityTypesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("EntityTypesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/environments/async_client.py b/google/cloud/dialogflowcx_v3beta1/services/environments/async_client.py index 6e6f2a85..ede9bc7b 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/environments/async_client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/environments/async_client.py @@ -517,7 +517,6 @@ async def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.CreateEnvironmentRequest( parent="parent_value", @@ -672,7 +671,6 @@ async def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.UpdateEnvironmentRequest( environment=environment, diff --git a/google/cloud/dialogflowcx_v3beta1/services/environments/client.py b/google/cloud/dialogflowcx_v3beta1/services/environments/client.py index a35d6421..2518d668 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/environments/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/environments/client.py @@ -59,6 +59,7 @@ from .transports.base import EnvironmentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import EnvironmentsGrpcTransport from .transports.grpc_asyncio import EnvironmentsGrpcAsyncIOTransport +from .transports.rest import EnvironmentsRestTransport class EnvironmentsClientMeta(type): @@ -72,6 +73,7 @@ class EnvironmentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[EnvironmentsTransport]] _transport_registry["grpc"] = EnvironmentsGrpcTransport _transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport + _transport_registry["rest"] = EnvironmentsRestTransport def get_transport_class( cls, @@ -880,7 +882,6 @@ def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.CreateEnvironmentRequest( parent="parent_value", @@ -1035,7 +1036,6 @@ def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.UpdateEnvironmentRequest( environment=environment, diff --git a/google/cloud/dialogflowcx_v3beta1/services/environments/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/environments/transports/__init__.py index 0ecefb61..1f36326c 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/environments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/environments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import EnvironmentsTransport from .grpc import EnvironmentsGrpcTransport from .grpc_asyncio import EnvironmentsGrpcAsyncIOTransport +from .rest import EnvironmentsRestTransport +from .rest import EnvironmentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[EnvironmentsTransport]] _transport_registry["grpc"] = EnvironmentsGrpcTransport _transport_registry["grpc_asyncio"] = EnvironmentsGrpcAsyncIOTransport +_transport_registry["rest"] = EnvironmentsRestTransport __all__ = ( "EnvironmentsTransport", "EnvironmentsGrpcTransport", "EnvironmentsGrpcAsyncIOTransport", + "EnvironmentsRestTransport", + "EnvironmentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/environments/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/environments/transports/rest.py new file mode 100644 index 00000000..5385c324 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/environments/transports/rest.py @@ -0,0 +1,1909 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import environment +from google.cloud.dialogflowcx_v3beta1.types import environment as gcdc_environment +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import EnvironmentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class EnvironmentsRestInterceptor: + """Interceptor for Environments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the EnvironmentsRestTransport. + + .. code-block:: python + class MyCustomEnvironmentsInterceptor(EnvironmentsRestInterceptor): + def pre_create_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_environment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_deploy_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_deploy_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_environment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_continuous_test_results(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_continuous_test_results(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_environments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_environments(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_lookup_environment_history(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_lookup_environment_history(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_run_continuous_test(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_run_continuous_test(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_environment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_environment(self, response): + logging.log(f"Received response: {response}") + return response + + transport = EnvironmentsRestTransport(interceptor=MyCustomEnvironmentsInterceptor()) + client = EnvironmentsClient(transport=transport) + + + """ + + def pre_create_environment( + self, + request: gcdc_environment.CreateEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_environment.CreateEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_create_environment( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for create_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_delete_environment( + self, + request: environment.DeleteEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.DeleteEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def pre_deploy_flow( + self, + request: environment.DeployFlowRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.DeployFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for deploy_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_deploy_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for deploy_flow + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_environment( + self, + request: environment.GetEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.GetEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_environment( + self, response: environment.Environment + ) -> environment.Environment: + """Post-rpc interceptor for get_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_continuous_test_results( + self, + request: environment.ListContinuousTestResultsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.ListContinuousTestResultsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_continuous_test_results + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_continuous_test_results( + self, response: environment.ListContinuousTestResultsResponse + ) -> environment.ListContinuousTestResultsResponse: + """Post-rpc interceptor for list_continuous_test_results + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_environments( + self, + request: environment.ListEnvironmentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.ListEnvironmentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_environments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_environments( + self, response: environment.ListEnvironmentsResponse + ) -> environment.ListEnvironmentsResponse: + """Post-rpc interceptor for list_environments + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_lookup_environment_history( + self, + request: environment.LookupEnvironmentHistoryRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.LookupEnvironmentHistoryRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for lookup_environment_history + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_lookup_environment_history( + self, response: environment.LookupEnvironmentHistoryResponse + ) -> environment.LookupEnvironmentHistoryResponse: + """Post-rpc interceptor for lookup_environment_history + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_run_continuous_test( + self, + request: environment.RunContinuousTestRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[environment.RunContinuousTestRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for run_continuous_test + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_run_continuous_test( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for run_continuous_test + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_update_environment( + self, + request: gcdc_environment.UpdateEnvironmentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_environment.UpdateEnvironmentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_environment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_update_environment( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for update_environment + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Environments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Environments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class EnvironmentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: EnvironmentsRestInterceptor + + +class EnvironmentsRestTransport(EnvironmentsTransport): + """REST backend transport for Environments. + + Service for managing + [Environments][google.cloud.dialogflow.cx.v3beta1.Environment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[EnvironmentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or EnvironmentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3beta1", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("CreateEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_environment.CreateEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the create environment method over HTTP. + + Args: + request (~.gcdc_environment.CreateEnvironmentRequest): + The request object. The request message for + [Environments.CreateEnvironment][google.cloud.dialogflow.cx.v3beta1.Environments.CreateEnvironment]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/environments", + "body": "environment", + }, + ] + request, metadata = self._interceptor.pre_create_environment( + request, metadata + ) + pb_request = gcdc_environment.CreateEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_environment(resp) + return resp + + class _DeleteEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("DeleteEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.DeleteEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete environment method over HTTP. + + Args: + request (~.environment.DeleteEnvironmentRequest): + The request object. The request message for + [Environments.DeleteEnvironment][google.cloud.dialogflow.cx.v3beta1.Environments.DeleteEnvironment]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_environment( + request, metadata + ) + pb_request = environment.DeleteEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _DeployFlow(EnvironmentsRestStub): + def __hash__(self): + return hash("DeployFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.DeployFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the deploy flow method over HTTP. + + Args: + request (~.environment.DeployFlowRequest): + The request object. The request message for + [Environments.DeployFlow][google.cloud.dialogflow.cx.v3beta1.Environments.DeployFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{environment=projects/*/locations/*/agents/*/environments/*}:deployFlow", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_deploy_flow(request, metadata) + pb_request = environment.DeployFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_deploy_flow(resp) + return resp + + class _GetEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("GetEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.GetEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.Environment: + r"""Call the get environment method over HTTP. + + Args: + request (~.environment.GetEnvironmentRequest): + The request object. The request message for + [Environments.GetEnvironment][google.cloud.dialogflow.cx.v3beta1.Environments.GetEnvironment]. + + 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: + ~.environment.Environment: + Represents an environment for an + agent. You can create multiple versions + of your agent and publish them to + separate environments. When you edit an + agent, you are editing the draft agent. + At any point, you can save the draft + agent as an agent version, which is an + immutable snapshot of your agent. When + you save the draft agent, it is + published to the default environment. + When you create agent versions, you can + publish them to custom environments. You + can create a variety of custom + environments for testing, development, + production, etc. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_environment(request, metadata) + pb_request = environment.GetEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.Environment() + pb_resp = environment.Environment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_environment(resp) + return resp + + class _ListContinuousTestResults(EnvironmentsRestStub): + def __hash__(self): + return hash("ListContinuousTestResults") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.ListContinuousTestResultsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.ListContinuousTestResultsResponse: + r"""Call the list continuous test + results method over HTTP. + + Args: + request (~.environment.ListContinuousTestResultsRequest): + The request object. The request message for + [Environments.ListContinuousTestResults][google.cloud.dialogflow.cx.v3beta1.Environments.ListContinuousTestResults]. + + 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: + ~.environment.ListContinuousTestResultsResponse: + The response message for + [Environments.ListTestCaseResults][]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/continuousTestResults", + }, + ] + request, metadata = self._interceptor.pre_list_continuous_test_results( + request, metadata + ) + pb_request = environment.ListContinuousTestResultsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.ListContinuousTestResultsResponse() + pb_resp = environment.ListContinuousTestResultsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_continuous_test_results(resp) + return resp + + class _ListEnvironments(EnvironmentsRestStub): + def __hash__(self): + return hash("ListEnvironments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.ListEnvironmentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.ListEnvironmentsResponse: + r"""Call the list environments method over HTTP. + + Args: + request (~.environment.ListEnvironmentsRequest): + The request object. The request message for + [Environments.ListEnvironments][google.cloud.dialogflow.cx.v3beta1.Environments.ListEnvironments]. + + 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: + ~.environment.ListEnvironmentsResponse: + The response message for + [Environments.ListEnvironments][google.cloud.dialogflow.cx.v3beta1.Environments.ListEnvironments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/environments", + }, + ] + request, metadata = self._interceptor.pre_list_environments( + request, metadata + ) + pb_request = environment.ListEnvironmentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.ListEnvironmentsResponse() + pb_resp = environment.ListEnvironmentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_environments(resp) + return resp + + class _LookupEnvironmentHistory(EnvironmentsRestStub): + def __hash__(self): + return hash("LookupEnvironmentHistory") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.LookupEnvironmentHistoryRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> environment.LookupEnvironmentHistoryResponse: + r"""Call the lookup environment + history method over HTTP. + + Args: + request (~.environment.LookupEnvironmentHistoryRequest): + The request object. The request message for + [Environments.LookupEnvironmentHistory][google.cloud.dialogflow.cx.v3beta1.Environments.LookupEnvironmentHistory]. + + 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: + ~.environment.LookupEnvironmentHistoryResponse: + The response message for + [Environments.LookupEnvironmentHistory][google.cloud.dialogflow.cx.v3beta1.Environments.LookupEnvironmentHistory]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}:lookupEnvironmentHistory", + }, + ] + request, metadata = self._interceptor.pre_lookup_environment_history( + request, metadata + ) + pb_request = environment.LookupEnvironmentHistoryRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = environment.LookupEnvironmentHistoryResponse() + pb_resp = environment.LookupEnvironmentHistoryResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_lookup_environment_history(resp) + return resp + + class _RunContinuousTest(EnvironmentsRestStub): + def __hash__(self): + return hash("RunContinuousTest") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: environment.RunContinuousTestRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the run continuous test method over HTTP. + + Args: + request (~.environment.RunContinuousTestRequest): + The request object. The request message for + [Environments.RunContinuousTest][google.cloud.dialogflow.cx.v3beta1.Environments.RunContinuousTest]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{environment=projects/*/locations/*/agents/*/environments/*}:runContinuousTest", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_run_continuous_test( + request, metadata + ) + pb_request = environment.RunContinuousTestRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_run_continuous_test(resp) + return resp + + class _UpdateEnvironment(EnvironmentsRestStub): + def __hash__(self): + return hash("UpdateEnvironment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_environment.UpdateEnvironmentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the update environment method over HTTP. + + Args: + request (~.gcdc_environment.UpdateEnvironmentRequest): + The request object. The request message for + [Environments.UpdateEnvironment][google.cloud.dialogflow.cx.v3beta1.Environments.UpdateEnvironment]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{environment.name=projects/*/locations/*/agents/*/environments/*}", + "body": "environment", + }, + ] + request, metadata = self._interceptor.pre_update_environment( + request, metadata + ) + pb_request = gcdc_environment.UpdateEnvironmentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_environment(resp) + return resp + + @property + def create_environment( + self, + ) -> Callable[ + [gcdc_environment.CreateEnvironmentRequest], operations_pb2.Operation + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_environment( + self, + ) -> Callable[[environment.DeleteEnvironmentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def deploy_flow( + self, + ) -> Callable[[environment.DeployFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeployFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_environment( + self, + ) -> Callable[[environment.GetEnvironmentRequest], environment.Environment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_continuous_test_results( + self, + ) -> Callable[ + [environment.ListContinuousTestResultsRequest], + environment.ListContinuousTestResultsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListContinuousTestResults(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_environments( + self, + ) -> Callable[ + [environment.ListEnvironmentsRequest], environment.ListEnvironmentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListEnvironments(self._session, self._host, self._interceptor) # type: ignore + + @property + def lookup_environment_history( + self, + ) -> Callable[ + [environment.LookupEnvironmentHistoryRequest], + environment.LookupEnvironmentHistoryResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._LookupEnvironmentHistory(self._session, self._host, self._interceptor) # type: ignore + + @property + def run_continuous_test( + self, + ) -> Callable[[environment.RunContinuousTestRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RunContinuousTest(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_environment( + self, + ) -> Callable[ + [gcdc_environment.UpdateEnvironmentRequest], operations_pb2.Operation + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateEnvironment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(EnvironmentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(EnvironmentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(EnvironmentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("EnvironmentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/experiments/client.py b/google/cloud/dialogflowcx_v3beta1/services/experiments/client.py index 271d2985..daef0eb8 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/experiments/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/experiments/client.py @@ -57,6 +57,7 @@ from .transports.base import ExperimentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ExperimentsGrpcTransport from .transports.grpc_asyncio import ExperimentsGrpcAsyncIOTransport +from .transports.rest import ExperimentsRestTransport class ExperimentsClientMeta(type): @@ -70,6 +71,7 @@ class ExperimentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[ExperimentsTransport]] _transport_registry["grpc"] = ExperimentsGrpcTransport _transport_registry["grpc_asyncio"] = ExperimentsGrpcAsyncIOTransport + _transport_registry["rest"] = ExperimentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/__init__.py index 3a6183c3..7aeb3d45 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/__init__.py @@ -19,15 +19,20 @@ from .base import ExperimentsTransport from .grpc import ExperimentsGrpcTransport from .grpc_asyncio import ExperimentsGrpcAsyncIOTransport +from .rest import ExperimentsRestTransport +from .rest import ExperimentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[ExperimentsTransport]] _transport_registry["grpc"] = ExperimentsGrpcTransport _transport_registry["grpc_asyncio"] = ExperimentsGrpcAsyncIOTransport +_transport_registry["rest"] = ExperimentsRestTransport __all__ = ( "ExperimentsTransport", "ExperimentsGrpcTransport", "ExperimentsGrpcAsyncIOTransport", + "ExperimentsRestTransport", + "ExperimentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/rest.py new file mode 100644 index 00000000..0477a862 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/experiments/transports/rest.py @@ -0,0 +1,1569 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import experiment +from google.cloud.dialogflowcx_v3beta1.types import experiment as gcdc_experiment +from google.protobuf import empty_pb2 # type: ignore + +from .base import ExperimentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class ExperimentsRestInterceptor: + """Interceptor for Experiments. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ExperimentsRestTransport. + + .. code-block:: python + class MyCustomExperimentsInterceptor(ExperimentsRestInterceptor): + def pre_create_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_experiments(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_experiments(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_start_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_start_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_stop_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_stop_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_experiment(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_experiment(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ExperimentsRestTransport(interceptor=MyCustomExperimentsInterceptor()) + client = ExperimentsClient(transport=transport) + + + """ + + def pre_create_experiment( + self, + request: gcdc_experiment.CreateExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_experiment.CreateExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_create_experiment( + self, response: gcdc_experiment.Experiment + ) -> gcdc_experiment.Experiment: + """Post-rpc interceptor for create_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_delete_experiment( + self, + request: experiment.DeleteExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.DeleteExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def pre_get_experiment( + self, + request: experiment.GetExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.GetExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for get_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_experiments( + self, + request: experiment.ListExperimentsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.ListExperimentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_experiments + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_experiments( + self, response: experiment.ListExperimentsResponse + ) -> experiment.ListExperimentsResponse: + """Post-rpc interceptor for list_experiments + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_start_experiment( + self, + request: experiment.StartExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.StartExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for start_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_start_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for start_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_stop_experiment( + self, + request: experiment.StopExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[experiment.StopExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for stop_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_stop_experiment( + self, response: experiment.Experiment + ) -> experiment.Experiment: + """Post-rpc interceptor for stop_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_update_experiment( + self, + request: gcdc_experiment.UpdateExperimentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_experiment.UpdateExperimentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_experiment + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_update_experiment( + self, response: gcdc_experiment.Experiment + ) -> gcdc_experiment.Experiment: + """Post-rpc interceptor for update_experiment + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Experiments server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Experiments server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class ExperimentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ExperimentsRestInterceptor + + +class ExperimentsRestTransport(ExperimentsTransport): + """REST backend transport for Experiments. + + Service for managing + [Experiments][google.cloud.dialogflow.cx.v3beta1.Experiment]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ExperimentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ExperimentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("CreateExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_experiment.CreateExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_experiment.Experiment: + r"""Call the create experiment method over HTTP. + + Args: + request (~.gcdc_experiment.CreateExperimentRequest): + The request object. The request message for + [Experiments.CreateExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.CreateExperiment]. + + 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: + ~.gcdc_experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/experiments", + "body": "experiment", + }, + ] + request, metadata = self._interceptor.pre_create_experiment( + request, metadata + ) + pb_request = gcdc_experiment.CreateExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_experiment.Experiment() + pb_resp = gcdc_experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_experiment(resp) + return resp + + class _DeleteExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("DeleteExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.DeleteExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete experiment method over HTTP. + + Args: + request (~.experiment.DeleteExperimentRequest): + The request object. The request message for + [Experiments.DeleteExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.DeleteExperiment]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_experiment( + request, metadata + ) + pb_request = experiment.DeleteExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("GetExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.GetExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the get experiment method over HTTP. + + Args: + request (~.experiment.GetExperimentRequest): + The request object. The request message for + [Experiments.GetExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.GetExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + }, + ] + request, metadata = self._interceptor.pre_get_experiment(request, metadata) + pb_request = experiment.GetExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_experiment(resp) + return resp + + class _ListExperiments(ExperimentsRestStub): + def __hash__(self): + return hash("ListExperiments") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.ListExperimentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.ListExperimentsResponse: + r"""Call the list experiments method over HTTP. + + Args: + request (~.experiment.ListExperimentsRequest): + The request object. The request message for + [Experiments.ListExperiments][google.cloud.dialogflow.cx.v3beta1.Experiments.ListExperiments]. + + 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: + ~.experiment.ListExperimentsResponse: + The response message for + [Experiments.ListExperiments][google.cloud.dialogflow.cx.v3beta1.Experiments.ListExperiments]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/experiments", + }, + ] + request, metadata = self._interceptor.pre_list_experiments( + request, metadata + ) + pb_request = experiment.ListExperimentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.ListExperimentsResponse() + pb_resp = experiment.ListExperimentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_experiments(resp) + return resp + + class _StartExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("StartExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.StartExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the start experiment method over HTTP. + + Args: + request (~.experiment.StartExperimentRequest): + The request object. The request message for + [Experiments.StartExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.StartExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:start", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_start_experiment( + request, metadata + ) + pb_request = experiment.StartExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_start_experiment(resp) + return resp + + class _StopExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("StopExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: experiment.StopExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> experiment.Experiment: + r"""Call the stop experiment method over HTTP. + + Args: + request (~.experiment.StopExperimentRequest): + The request object. The request message for + [Experiments.StopExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.StopExperiment]. + + 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: + ~.experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:stop", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_stop_experiment(request, metadata) + pb_request = experiment.StopExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = experiment.Experiment() + pb_resp = experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_stop_experiment(resp) + return resp + + class _UpdateExperiment(ExperimentsRestStub): + def __hash__(self): + return hash("UpdateExperiment") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_experiment.UpdateExperimentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_experiment.Experiment: + r"""Call the update experiment method over HTTP. + + Args: + request (~.gcdc_experiment.UpdateExperimentRequest): + The request object. The request message for + [Experiments.UpdateExperiment][google.cloud.dialogflow.cx.v3beta1.Experiments.UpdateExperiment]. + + 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: + ~.gcdc_experiment.Experiment: + Represents an experiment in an + environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{experiment.name=projects/*/locations/*/agents/*/environments/*/experiments/*}", + "body": "experiment", + }, + ] + request, metadata = self._interceptor.pre_update_experiment( + request, metadata + ) + pb_request = gcdc_experiment.UpdateExperimentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_experiment.Experiment() + pb_resp = gcdc_experiment.Experiment.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_experiment(resp) + return resp + + @property + def create_experiment( + self, + ) -> Callable[ + [gcdc_experiment.CreateExperimentRequest], gcdc_experiment.Experiment + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_experiment( + self, + ) -> Callable[[experiment.DeleteExperimentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_experiment( + self, + ) -> Callable[[experiment.GetExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_experiments( + self, + ) -> Callable[ + [experiment.ListExperimentsRequest], experiment.ListExperimentsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListExperiments(self._session, self._host, self._interceptor) # type: ignore + + @property + def start_experiment( + self, + ) -> Callable[[experiment.StartExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StartExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def stop_experiment( + self, + ) -> Callable[[experiment.StopExperimentRequest], experiment.Experiment]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StopExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_experiment( + self, + ) -> Callable[ + [gcdc_experiment.UpdateExperimentRequest], gcdc_experiment.Experiment + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateExperiment(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(ExperimentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(ExperimentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(ExperimentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ExperimentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/flows/client.py b/google/cloud/dialogflowcx_v3beta1/services/flows/client.py index 6c1e637f..5dde6d9a 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/flows/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/flows/client.py @@ -62,6 +62,7 @@ from .transports.base import FlowsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import FlowsGrpcTransport from .transports.grpc_asyncio import FlowsGrpcAsyncIOTransport +from .transports.rest import FlowsRestTransport class FlowsClientMeta(type): @@ -75,6 +76,7 @@ class FlowsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[FlowsTransport]] _transport_registry["grpc"] = FlowsGrpcTransport _transport_registry["grpc_asyncio"] = FlowsGrpcAsyncIOTransport + _transport_registry["rest"] = FlowsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/flows/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/flows/transports/__init__.py index d27c76de..5a0a6683 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/flows/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/flows/transports/__init__.py @@ -19,15 +19,20 @@ from .base import FlowsTransport from .grpc import FlowsGrpcTransport from .grpc_asyncio import FlowsGrpcAsyncIOTransport +from .rest import FlowsRestTransport +from .rest import FlowsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[FlowsTransport]] _transport_registry["grpc"] = FlowsGrpcTransport _transport_registry["grpc_asyncio"] = FlowsGrpcAsyncIOTransport +_transport_registry["rest"] = FlowsRestTransport __all__ = ( "FlowsTransport", "FlowsGrpcTransport", "FlowsGrpcAsyncIOTransport", + "FlowsRestTransport", + "FlowsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/flows/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/flows/transports/rest.py new file mode 100644 index 00000000..efba88cb --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/flows/transports/rest.py @@ -0,0 +1,2044 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import flow +from google.cloud.dialogflowcx_v3beta1.types import flow as gcdc_flow +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import FlowsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class FlowsRestInterceptor: + """Interceptor for Flows. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the FlowsRestTransport. + + .. code-block:: python + class MyCustomFlowsInterceptor(FlowsRestInterceptor): + def pre_create_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_export_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_flow_validation_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_flow_validation_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_import_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_import_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_flows(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_flows(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_train_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_train_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_flow(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_flow(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_flow(self, response): + logging.log(f"Received response: {response}") + return response + + transport = FlowsRestTransport(interceptor=MyCustomFlowsInterceptor()) + client = FlowsClient(transport=transport) + + + """ + + def pre_create_flow( + self, request: gcdc_flow.CreateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_flow.CreateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_create_flow(self, response: gcdc_flow.Flow) -> gcdc_flow.Flow: + """Post-rpc interceptor for create_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_delete_flow( + self, request: flow.DeleteFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.DeleteFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def pre_export_flow( + self, request: flow.ExportFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ExportFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_export_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_flow( + self, request: flow.GetFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.GetFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_flow(self, response: flow.Flow) -> flow.Flow: + """Post-rpc interceptor for get_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_flow_validation_result( + self, + request: flow.GetFlowValidationResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[flow.GetFlowValidationResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_flow_validation_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_flow_validation_result( + self, response: flow.FlowValidationResult + ) -> flow.FlowValidationResult: + """Post-rpc interceptor for get_flow_validation_result + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_import_flow( + self, request: flow.ImportFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ImportFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for import_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_import_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for import_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_flows( + self, request: flow.ListFlowsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ListFlowsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_flows + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_flows( + self, response: flow.ListFlowsResponse + ) -> flow.ListFlowsResponse: + """Post-rpc interceptor for list_flows + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_train_flow( + self, request: flow.TrainFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.TrainFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for train_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_train_flow( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for train_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_update_flow( + self, request: gcdc_flow.UpdateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_flow.UpdateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_update_flow(self, response: gcdc_flow.Flow) -> gcdc_flow.Flow: + """Post-rpc interceptor for update_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_validate_flow( + self, request: flow.ValidateFlowRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[flow.ValidateFlowRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_flow + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_validate_flow( + self, response: flow.FlowValidationResult + ) -> flow.FlowValidationResult: + """Post-rpc interceptor for validate_flow + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Flows server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Flows server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class FlowsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: FlowsRestInterceptor + + +class FlowsRestTransport(FlowsTransport): + """REST backend transport for Flows. + + Service for managing + [Flows][google.cloud.dialogflow.cx.v3beta1.Flow]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[FlowsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or FlowsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3beta1", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CreateFlow(FlowsRestStub): + def __hash__(self): + return hash("CreateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_flow.CreateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_flow.Flow: + r"""Call the create flow method over HTTP. + + Args: + request (~.gcdc_flow.CreateFlowRequest): + The request object. The request message for + [Flows.CreateFlow][google.cloud.dialogflow.cx.v3beta1.Flows.CreateFlow]. + + 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: + ~.gcdc_flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/flows", + "body": "flow", + }, + ] + request, metadata = self._interceptor.pre_create_flow(request, metadata) + pb_request = gcdc_flow.CreateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_flow.Flow() + pb_resp = gcdc_flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_flow(resp) + return resp + + class _DeleteFlow(FlowsRestStub): + def __hash__(self): + return hash("DeleteFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.DeleteFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete flow method over HTTP. + + Args: + request (~.flow.DeleteFlowRequest): + The request object. The request message for + [Flows.DeleteFlow][google.cloud.dialogflow.cx.v3beta1.Flows.DeleteFlow]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_flow(request, metadata) + pb_request = flow.DeleteFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _ExportFlow(FlowsRestStub): + def __hash__(self): + return hash("ExportFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ExportFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export flow method over HTTP. + + Args: + request (~.flow.ExportFlowRequest): + The request object. The request message for + [Flows.ExportFlow][google.cloud.dialogflow.cx.v3beta1.Flows.ExportFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_flow(request, metadata) + pb_request = flow.ExportFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_flow(resp) + return resp + + class _GetFlow(FlowsRestStub): + def __hash__(self): + return hash("GetFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.GetFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.Flow: + r"""Call the get flow method over HTTP. + + Args: + request (~.flow.GetFlowRequest): + The request object. The response message for + [Flows.GetFlow][google.cloud.dialogflow.cx.v3beta1.Flows.GetFlow]. + + 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: + ~.flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}", + }, + ] + request, metadata = self._interceptor.pre_get_flow(request, metadata) + pb_request = flow.GetFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.Flow() + pb_resp = flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_flow(resp) + return resp + + class _GetFlowValidationResult(FlowsRestStub): + def __hash__(self): + return hash("GetFlowValidationResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.GetFlowValidationResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.FlowValidationResult: + r"""Call the get flow validation + result method over HTTP. + + Args: + request (~.flow.GetFlowValidationResultRequest): + The request object. The request message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3beta1.Flows.GetFlowValidationResult]. + + 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: + ~.flow.FlowValidationResult: + The response message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3beta1.Flows.GetFlowValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/validationResult}", + }, + ] + request, metadata = self._interceptor.pre_get_flow_validation_result( + request, metadata + ) + pb_request = flow.GetFlowValidationResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.FlowValidationResult() + pb_resp = flow.FlowValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_flow_validation_result(resp) + return resp + + class _ImportFlow(FlowsRestStub): + def __hash__(self): + return hash("ImportFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ImportFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the import flow method over HTTP. + + Args: + request (~.flow.ImportFlowRequest): + The request object. The request message for + [Flows.ImportFlow][google.cloud.dialogflow.cx.v3beta1.Flows.ImportFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/flows:import", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_import_flow(request, metadata) + pb_request = flow.ImportFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_import_flow(resp) + return resp + + class _ListFlows(FlowsRestStub): + def __hash__(self): + return hash("ListFlows") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ListFlowsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.ListFlowsResponse: + r"""Call the list flows method over HTTP. + + Args: + request (~.flow.ListFlowsRequest): + The request object. The request message for + [Flows.ListFlows][google.cloud.dialogflow.cx.v3beta1.Flows.ListFlows]. + + 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: + ~.flow.ListFlowsResponse: + The response message for + [Flows.ListFlows][google.cloud.dialogflow.cx.v3beta1.Flows.ListFlows]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/flows", + }, + ] + request, metadata = self._interceptor.pre_list_flows(request, metadata) + pb_request = flow.ListFlowsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.ListFlowsResponse() + pb_resp = flow.ListFlowsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_flows(resp) + return resp + + class _TrainFlow(FlowsRestStub): + def __hash__(self): + return hash("TrainFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.TrainFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the train flow method over HTTP. + + Args: + request (~.flow.TrainFlowRequest): + The request object. The request message for + [Flows.TrainFlow][google.cloud.dialogflow.cx.v3beta1.Flows.TrainFlow]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}:train", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_train_flow(request, metadata) + pb_request = flow.TrainFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_train_flow(resp) + return resp + + class _UpdateFlow(FlowsRestStub): + def __hash__(self): + return hash("UpdateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_flow.UpdateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_flow.Flow: + r"""Call the update flow method over HTTP. + + Args: + request (~.gcdc_flow.UpdateFlowRequest): + The request object. The request message for + [Flows.UpdateFlow][google.cloud.dialogflow.cx.v3beta1.Flows.UpdateFlow]. + + 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: + ~.gcdc_flow.Flow: + Flows represents the conversation + flows when you build your chatbot agent. + A flow consists of many pages connected + by the transition routes. Conversations + always start with the built-in Start + Flow (with an all-0 ID). Transition + routes can direct the conversation + session from the current flow (parent + flow) to another flow (sub flow). When + the sub flow is finished, Dialogflow + will bring the session back to the + parent flow, where the sub flow is + started. + + Usually, when a transition route is + followed by a matched intent, the intent + will be "consumed". This means the + intent won't activate more transition + routes. However, when the followed + transition route moves the conversation + session into a different flow, the + matched intent can be carried over and + to be consumed in the target flow. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{flow.name=projects/*/locations/*/agents/*/flows/*}", + "body": "flow", + }, + ] + request, metadata = self._interceptor.pre_update_flow(request, metadata) + pb_request = gcdc_flow.UpdateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_flow.Flow() + pb_resp = gcdc_flow.Flow.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_flow(resp) + return resp + + class _ValidateFlow(FlowsRestStub): + def __hash__(self): + return hash("ValidateFlow") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: flow.ValidateFlowRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> flow.FlowValidationResult: + r"""Call the validate flow method over HTTP. + + Args: + request (~.flow.ValidateFlowRequest): + The request object. The request message for + [Flows.ValidateFlow][google.cloud.dialogflow.cx.v3beta1.Flows.ValidateFlow]. + + 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: + ~.flow.FlowValidationResult: + The response message for + [Flows.GetFlowValidationResult][google.cloud.dialogflow.cx.v3beta1.Flows.GetFlowValidationResult]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}:validate", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_flow(request, metadata) + pb_request = flow.ValidateFlowRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = flow.FlowValidationResult() + pb_resp = flow.FlowValidationResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_flow(resp) + return resp + + @property + def create_flow(self) -> Callable[[gcdc_flow.CreateFlowRequest], gcdc_flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_flow(self) -> Callable[[flow.DeleteFlowRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_flow( + self, + ) -> Callable[[flow.ExportFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_flow(self) -> Callable[[flow.GetFlowRequest], flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_flow_validation_result( + self, + ) -> Callable[[flow.GetFlowValidationResultRequest], flow.FlowValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetFlowValidationResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def import_flow( + self, + ) -> Callable[[flow.ImportFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ImportFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_flows(self) -> Callable[[flow.ListFlowsRequest], flow.ListFlowsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListFlows(self._session, self._host, self._interceptor) # type: ignore + + @property + def train_flow(self) -> Callable[[flow.TrainFlowRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._TrainFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_flow(self) -> Callable[[gcdc_flow.UpdateFlowRequest], gcdc_flow.Flow]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_flow( + self, + ) -> Callable[[flow.ValidateFlowRequest], flow.FlowValidationResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ValidateFlow(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(FlowsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(FlowsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(FlowsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(FlowsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(FlowsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("FlowsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/intents/client.py b/google/cloud/dialogflowcx_v3beta1/services/intents/client.py index ec876b95..06041c58 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/intents/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/intents/client.py @@ -55,6 +55,7 @@ from .transports.base import IntentsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import IntentsGrpcTransport from .transports.grpc_asyncio import IntentsGrpcAsyncIOTransport +from .transports.rest import IntentsRestTransport class IntentsClientMeta(type): @@ -68,6 +69,7 @@ class IntentsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[IntentsTransport]] _transport_registry["grpc"] = IntentsGrpcTransport _transport_registry["grpc_asyncio"] = IntentsGrpcAsyncIOTransport + _transport_registry["rest"] = IntentsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/intents/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/intents/transports/__init__.py index 876b5cd1..a7e402a6 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/intents/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/intents/transports/__init__.py @@ -19,15 +19,20 @@ from .base import IntentsTransport from .grpc import IntentsGrpcTransport from .grpc_asyncio import IntentsGrpcAsyncIOTransport +from .rest import IntentsRestTransport +from .rest import IntentsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[IntentsTransport]] _transport_registry["grpc"] = IntentsGrpcTransport _transport_registry["grpc_asyncio"] = IntentsGrpcAsyncIOTransport +_transport_registry["rest"] = IntentsRestTransport __all__ = ( "IntentsTransport", "IntentsGrpcTransport", "IntentsGrpcAsyncIOTransport", + "IntentsRestTransport", + "IntentsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/intents/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/intents/transports/rest.py new file mode 100644 index 00000000..b793a934 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/intents/transports/rest.py @@ -0,0 +1,1274 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import intent +from google.cloud.dialogflowcx_v3beta1.types import intent as gcdc_intent +from google.protobuf import empty_pb2 # type: ignore + +from .base import IntentsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class IntentsRestInterceptor: + """Interceptor for Intents. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the IntentsRestTransport. + + .. code-block:: python + class MyCustomIntentsInterceptor(IntentsRestInterceptor): + def pre_create_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_intents(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_intents(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_intent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = IntentsRestTransport(interceptor=MyCustomIntentsInterceptor()) + client = IntentsClient(transport=transport) + + + """ + + def pre_create_intent( + self, + request: gcdc_intent.CreateIntentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_intent.CreateIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_create_intent(self, response: gcdc_intent.Intent) -> gcdc_intent.Intent: + """Post-rpc interceptor for create_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_delete_intent( + self, request: intent.DeleteIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.DeleteIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def pre_get_intent( + self, request: intent.GetIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.GetIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_intent(self, response: intent.Intent) -> intent.Intent: + """Post-rpc interceptor for get_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_intents( + self, request: intent.ListIntentsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[intent.ListIntentsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_intents + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_intents( + self, response: intent.ListIntentsResponse + ) -> intent.ListIntentsResponse: + """Post-rpc interceptor for list_intents + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_update_intent( + self, + request: gcdc_intent.UpdateIntentRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_intent.UpdateIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_update_intent(self, response: gcdc_intent.Intent) -> gcdc_intent.Intent: + """Post-rpc interceptor for update_intent + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Intents server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Intents server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class IntentsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: IntentsRestInterceptor + + +class IntentsRestTransport(IntentsTransport): + """REST backend transport for Intents. + + Service for managing + [Intents][google.cloud.dialogflow.cx.v3beta1.Intent]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[IntentsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or IntentsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateIntent(IntentsRestStub): + def __hash__(self): + return hash("CreateIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_intent.CreateIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_intent.Intent: + r"""Call the create intent method over HTTP. + + Args: + request (~.gcdc_intent.CreateIntentRequest): + The request object. The request message for + [Intents.CreateIntent][google.cloud.dialogflow.cx.v3beta1.Intents.CreateIntent]. + + 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: + ~.gcdc_intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/intents", + "body": "intent", + }, + ] + request, metadata = self._interceptor.pre_create_intent(request, metadata) + pb_request = gcdc_intent.CreateIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_intent.Intent() + pb_resp = gcdc_intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_intent(resp) + return resp + + class _DeleteIntent(IntentsRestStub): + def __hash__(self): + return hash("DeleteIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.DeleteIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete intent method over HTTP. + + Args: + request (~.intent.DeleteIntentRequest): + The request object. The request message for + [Intents.DeleteIntent][google.cloud.dialogflow.cx.v3beta1.Intents.DeleteIntent]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/intents/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_intent(request, metadata) + pb_request = intent.DeleteIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetIntent(IntentsRestStub): + def __hash__(self): + return hash("GetIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.GetIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> intent.Intent: + r"""Call the get intent method over HTTP. + + Args: + request (~.intent.GetIntentRequest): + The request object. The request message for + [Intents.GetIntent][google.cloud.dialogflow.cx.v3beta1.Intents.GetIntent]. + + 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: + ~.intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/intents/*}", + }, + ] + request, metadata = self._interceptor.pre_get_intent(request, metadata) + pb_request = intent.GetIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = intent.Intent() + pb_resp = intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_intent(resp) + return resp + + class _ListIntents(IntentsRestStub): + def __hash__(self): + return hash("ListIntents") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: intent.ListIntentsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> intent.ListIntentsResponse: + r"""Call the list intents method over HTTP. + + Args: + request (~.intent.ListIntentsRequest): + The request object. The request message for + [Intents.ListIntents][google.cloud.dialogflow.cx.v3beta1.Intents.ListIntents]. + + 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: + ~.intent.ListIntentsResponse: + The response message for + [Intents.ListIntents][google.cloud.dialogflow.cx.v3beta1.Intents.ListIntents]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/intents", + }, + ] + request, metadata = self._interceptor.pre_list_intents(request, metadata) + pb_request = intent.ListIntentsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = intent.ListIntentsResponse() + pb_resp = intent.ListIntentsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_intents(resp) + return resp + + class _UpdateIntent(IntentsRestStub): + def __hash__(self): + return hash("UpdateIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_intent.UpdateIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_intent.Intent: + r"""Call the update intent method over HTTP. + + Args: + request (~.gcdc_intent.UpdateIntentRequest): + The request object. The request message for + [Intents.UpdateIntent][google.cloud.dialogflow.cx.v3beta1.Intents.UpdateIntent]. + + 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: + ~.gcdc_intent.Intent: + An intent represents a user's intent + to interact with a conversational agent. + You can provide information for the + Dialogflow API to use to match user + input to an intent by adding training + phrases (i.e., examples of user input) + to your intent. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{intent.name=projects/*/locations/*/agents/*/intents/*}", + "body": "intent", + }, + ] + request, metadata = self._interceptor.pre_update_intent(request, metadata) + pb_request = gcdc_intent.UpdateIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_intent.Intent() + pb_resp = gcdc_intent.Intent.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_intent(resp) + return resp + + @property + def create_intent( + self, + ) -> Callable[[gcdc_intent.CreateIntentRequest], gcdc_intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_intent(self) -> Callable[[intent.DeleteIntentRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_intent(self) -> Callable[[intent.GetIntentRequest], intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_intents( + self, + ) -> Callable[[intent.ListIntentsRequest], intent.ListIntentsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListIntents(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_intent( + self, + ) -> Callable[[gcdc_intent.UpdateIntentRequest], gcdc_intent.Intent]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(IntentsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(IntentsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(IntentsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(IntentsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(IntentsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("IntentsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/pages/client.py b/google/cloud/dialogflowcx_v3beta1/services/pages/client.py index 6768a75a..2bf41727 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/pages/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/pages/client.py @@ -56,6 +56,7 @@ from .transports.base import PagesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import PagesGrpcTransport from .transports.grpc_asyncio import PagesGrpcAsyncIOTransport +from .transports.rest import PagesRestTransport class PagesClientMeta(type): @@ -69,6 +70,7 @@ class PagesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[PagesTransport]] _transport_registry["grpc"] = PagesGrpcTransport _transport_registry["grpc_asyncio"] = PagesGrpcAsyncIOTransport + _transport_registry["rest"] = PagesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/pages/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/pages/transports/__init__.py index fd23f06e..387c6a3a 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/pages/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/pages/transports/__init__.py @@ -19,15 +19,20 @@ from .base import PagesTransport from .grpc import PagesGrpcTransport from .grpc_asyncio import PagesGrpcAsyncIOTransport +from .rest import PagesRestTransport +from .rest import PagesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[PagesTransport]] _transport_registry["grpc"] = PagesGrpcTransport _transport_registry["grpc_asyncio"] = PagesGrpcAsyncIOTransport +_transport_registry["rest"] = PagesRestTransport __all__ = ( "PagesTransport", "PagesGrpcTransport", "PagesGrpcAsyncIOTransport", + "PagesRestTransport", + "PagesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/pages/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/pages/transports/rest.py new file mode 100644 index 00000000..9ee68d25 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/pages/transports/rest.py @@ -0,0 +1,1306 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import page +from google.cloud.dialogflowcx_v3beta1.types import page as gcdc_page +from google.protobuf import empty_pb2 # type: ignore + +from .base import PagesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class PagesRestInterceptor: + """Interceptor for Pages. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the PagesRestTransport. + + .. code-block:: python + class MyCustomPagesInterceptor(PagesRestInterceptor): + def pre_create_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_page(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_page(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_pages(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_pages(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_page(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_page(self, response): + logging.log(f"Received response: {response}") + return response + + transport = PagesRestTransport(interceptor=MyCustomPagesInterceptor()) + client = PagesClient(transport=transport) + + + """ + + def pre_create_page( + self, request: gcdc_page.CreatePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_page.CreatePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_create_page(self, response: gcdc_page.Page) -> gcdc_page.Page: + """Post-rpc interceptor for create_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_delete_page( + self, request: page.DeletePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.DeletePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def pre_get_page( + self, request: page.GetPageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.GetPageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_page(self, response: page.Page) -> page.Page: + """Post-rpc interceptor for get_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_pages( + self, request: page.ListPagesRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[page.ListPagesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_pages + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_pages( + self, response: page.ListPagesResponse + ) -> page.ListPagesResponse: + """Post-rpc interceptor for list_pages + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_update_page( + self, request: gcdc_page.UpdatePageRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[gcdc_page.UpdatePageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_page + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_update_page(self, response: gcdc_page.Page) -> gcdc_page.Page: + """Post-rpc interceptor for update_page + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Pages server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Pages server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class PagesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: PagesRestInterceptor + + +class PagesRestTransport(PagesTransport): + """REST backend transport for Pages. + + Service for managing + [Pages][google.cloud.dialogflow.cx.v3beta1.Page]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[PagesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or PagesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreatePage(PagesRestStub): + def __hash__(self): + return hash("CreatePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_page.CreatePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_page.Page: + r"""Call the create page method over HTTP. + + Args: + request (~.gcdc_page.CreatePageRequest): + The request object. The request message for + [Pages.CreatePage][google.cloud.dialogflow.cx.v3beta1.Pages.CreatePage]. + + 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: + ~.gcdc_page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/pages", + "body": "page", + }, + ] + request, metadata = self._interceptor.pre_create_page(request, metadata) + pb_request = gcdc_page.CreatePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_page.Page() + pb_resp = gcdc_page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_page(resp) + return resp + + class _DeletePage(PagesRestStub): + def __hash__(self): + return hash("DeletePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.DeletePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete page method over HTTP. + + Args: + request (~.page.DeletePageRequest): + The request object. The request message for + [Pages.DeletePage][google.cloud.dialogflow.cx.v3beta1.Pages.DeletePage]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/pages/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_page(request, metadata) + pb_request = page.DeletePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetPage(PagesRestStub): + def __hash__(self): + return hash("GetPage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.GetPageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> page.Page: + r"""Call the get page method over HTTP. + + Args: + request (~.page.GetPageRequest): + The request object. The request message for + [Pages.GetPage][google.cloud.dialogflow.cx.v3beta1.Pages.GetPage]. + + 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: + ~.page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/pages/*}", + }, + ] + request, metadata = self._interceptor.pre_get_page(request, metadata) + pb_request = page.GetPageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = page.Page() + pb_resp = page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_page(resp) + return resp + + class _ListPages(PagesRestStub): + def __hash__(self): + return hash("ListPages") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: page.ListPagesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> page.ListPagesResponse: + r"""Call the list pages method over HTTP. + + Args: + request (~.page.ListPagesRequest): + The request object. The request message for + [Pages.ListPages][google.cloud.dialogflow.cx.v3beta1.Pages.ListPages]. + + 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: + ~.page.ListPagesResponse: + The response message for + [Pages.ListPages][google.cloud.dialogflow.cx.v3beta1.Pages.ListPages]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/pages", + }, + ] + request, metadata = self._interceptor.pre_list_pages(request, metadata) + pb_request = page.ListPagesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = page.ListPagesResponse() + pb_resp = page.ListPagesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_pages(resp) + return resp + + class _UpdatePage(PagesRestStub): + def __hash__(self): + return hash("UpdatePage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_page.UpdatePageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_page.Page: + r"""Call the update page method over HTTP. + + Args: + request (~.gcdc_page.UpdatePageRequest): + The request object. The request message for + [Pages.UpdatePage][google.cloud.dialogflow.cx.v3beta1.Pages.UpdatePage]. + + 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: + ~.gcdc_page.Page: + A Dialogflow CX conversation (session) can be described + and visualized as a state machine. The states of a CX + session are represented by pages. + + For each flow, you define many pages, where your + combined pages can handle a complete conversation on the + topics the flow is designed for. At any given moment, + exactly one page is the current page, the current page + is considered active, and the flow associated with that + page is considered active. Every flow has a special + start page. When a flow initially becomes active, the + start page page becomes the current page. For each + conversational turn, the current page will either stay + the same or transition to another page. + + You configure each page to collect information from the + end-user that is relevant for the conversational state + represented by the page. + + For more information, see the `Page + guide `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{page.name=projects/*/locations/*/agents/*/flows/*/pages/*}", + "body": "page", + }, + ] + request, metadata = self._interceptor.pre_update_page(request, metadata) + pb_request = gcdc_page.UpdatePageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_page.Page() + pb_resp = gcdc_page.Page.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_page(resp) + return resp + + @property + def create_page(self) -> Callable[[gcdc_page.CreatePageRequest], gcdc_page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreatePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_page(self) -> Callable[[page.DeletePageRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeletePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_page(self) -> Callable[[page.GetPageRequest], page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetPage(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_pages(self) -> Callable[[page.ListPagesRequest], page.ListPagesResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListPages(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_page(self) -> Callable[[gcdc_page.UpdatePageRequest], gcdc_page.Page]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdatePage(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(PagesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(PagesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(PagesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(PagesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(PagesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("PagesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/client.py b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/client.py index 7651ec43..7d1e534f 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/client.py @@ -57,6 +57,7 @@ from .transports.base import SecuritySettingsServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SecuritySettingsServiceGrpcTransport from .transports.grpc_asyncio import SecuritySettingsServiceGrpcAsyncIOTransport +from .transports.rest import SecuritySettingsServiceRestTransport class SecuritySettingsServiceClientMeta(type): @@ -72,6 +73,7 @@ class SecuritySettingsServiceClientMeta(type): ) # type: Dict[str, Type[SecuritySettingsServiceTransport]] _transport_registry["grpc"] = SecuritySettingsServiceGrpcTransport _transport_registry["grpc_asyncio"] = SecuritySettingsServiceGrpcAsyncIOTransport + _transport_registry["rest"] = SecuritySettingsServiceRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/__init__.py index bcf597b1..8bb194f4 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/__init__.py @@ -19,6 +19,8 @@ from .base import SecuritySettingsServiceTransport from .grpc import SecuritySettingsServiceGrpcTransport from .grpc_asyncio import SecuritySettingsServiceGrpcAsyncIOTransport +from .rest import SecuritySettingsServiceRestTransport +from .rest import SecuritySettingsServiceRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[SecuritySettingsServiceTransport]] _transport_registry["grpc"] = SecuritySettingsServiceGrpcTransport _transport_registry["grpc_asyncio"] = SecuritySettingsServiceGrpcAsyncIOTransport +_transport_registry["rest"] = SecuritySettingsServiceRestTransport __all__ = ( "SecuritySettingsServiceTransport", "SecuritySettingsServiceGrpcTransport", "SecuritySettingsServiceGrpcAsyncIOTransport", + "SecuritySettingsServiceRestTransport", + "SecuritySettingsServiceRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/rest.py new file mode 100644 index 00000000..bc1a087c --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/security_settings_service/transports/rest.py @@ -0,0 +1,1327 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import security_settings +from google.cloud.dialogflowcx_v3beta1.types import ( + security_settings as gcdc_security_settings, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + SecuritySettingsServiceTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SecuritySettingsServiceRestInterceptor: + """Interceptor for SecuritySettingsService. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SecuritySettingsServiceRestTransport. + + .. code-block:: python + class MyCustomSecuritySettingsServiceInterceptor(SecuritySettingsServiceRestInterceptor): + def pre_create_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_security_settings(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_security_settings(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SecuritySettingsServiceRestTransport(interceptor=MyCustomSecuritySettingsServiceInterceptor()) + client = SecuritySettingsServiceClient(transport=transport) + + + """ + + def pre_create_security_settings( + self, + request: gcdc_security_settings.CreateSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_security_settings.CreateSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for create_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_create_security_settings( + self, response: gcdc_security_settings.SecuritySettings + ) -> gcdc_security_settings.SecuritySettings: + """Post-rpc interceptor for create_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_delete_security_settings( + self, + request: security_settings.DeleteSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + security_settings.DeleteSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for delete_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def pre_get_security_settings( + self, + request: security_settings.GetSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[security_settings.GetSecuritySettingsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_security_settings( + self, response: security_settings.SecuritySettings + ) -> security_settings.SecuritySettings: + """Post-rpc interceptor for get_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_security_settings( + self, + request: security_settings.ListSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + security_settings.ListSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for list_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_security_settings( + self, response: security_settings.ListSecuritySettingsResponse + ) -> security_settings.ListSecuritySettingsResponse: + """Post-rpc interceptor for list_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_update_security_settings( + self, + request: gcdc_security_settings.UpdateSecuritySettingsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_security_settings.UpdateSecuritySettingsRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for update_security_settings + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_update_security_settings( + self, response: gcdc_security_settings.SecuritySettings + ) -> gcdc_security_settings.SecuritySettings: + """Post-rpc interceptor for update_security_settings + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SecuritySettingsService server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the SecuritySettingsService server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SecuritySettingsServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SecuritySettingsServiceRestInterceptor + + +class SecuritySettingsServiceRestTransport(SecuritySettingsServiceTransport): + """REST backend transport for SecuritySettingsService. + + Service for managing security settings for Dialogflow. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SecuritySettingsServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SecuritySettingsServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("CreateSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_security_settings.CreateSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_security_settings.SecuritySettings: + r"""Call the create security settings method over HTTP. + + Args: + request (~.gcdc_security_settings.CreateSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.CreateSecuritySettings][]. + + 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: + ~.gcdc_security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*}/securitySettings", + "body": "security_settings", + }, + ] + request, metadata = self._interceptor.pre_create_security_settings( + request, metadata + ) + pb_request = gcdc_security_settings.CreateSecuritySettingsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_security_settings.SecuritySettings() + pb_resp = gcdc_security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_security_settings(resp) + return resp + + class _DeleteSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("DeleteSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.DeleteSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete security settings method over HTTP. + + Args: + request (~.security_settings.DeleteSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.DeleteSecuritySettings][]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/securitySettings/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_security_settings( + request, metadata + ) + pb_request = security_settings.DeleteSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("GetSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.GetSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> security_settings.SecuritySettings: + r"""Call the get security settings method over HTTP. + + Args: + request (~.security_settings.GetSecuritySettingsRequest): + The request object. The request message for + [SecuritySettingsService.GetSecuritySettings][google.cloud.dialogflow.cx.v3beta1.SecuritySettingsService.GetSecuritySettings]. + + 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: + ~.security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/securitySettings/*}", + }, + ] + request, metadata = self._interceptor.pre_get_security_settings( + request, metadata + ) + pb_request = security_settings.GetSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = security_settings.SecuritySettings() + pb_resp = security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_security_settings(resp) + return resp + + class _ListSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("ListSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: security_settings.ListSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> security_settings.ListSecuritySettingsResponse: + r"""Call the list security settings method over HTTP. + + Args: + request (~.security_settings.ListSecuritySettingsRequest): + The request object. The request message for + [SecuritySettings.ListSecuritySettings][]. + + 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: + ~.security_settings.ListSecuritySettingsResponse: + The response message for + [SecuritySettings.ListSecuritySettings][]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*}/securitySettings", + }, + ] + request, metadata = self._interceptor.pre_list_security_settings( + request, metadata + ) + pb_request = security_settings.ListSecuritySettingsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = security_settings.ListSecuritySettingsResponse() + pb_resp = security_settings.ListSecuritySettingsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_security_settings(resp) + return resp + + class _UpdateSecuritySettings(SecuritySettingsServiceRestStub): + def __hash__(self): + return hash("UpdateSecuritySettings") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_security_settings.UpdateSecuritySettingsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_security_settings.SecuritySettings: + r"""Call the update security settings method over HTTP. + + Args: + request (~.gcdc_security_settings.UpdateSecuritySettingsRequest): + The request object. The request message for + [SecuritySettingsService.UpdateSecuritySettings][google.cloud.dialogflow.cx.v3beta1.SecuritySettingsService.UpdateSecuritySettings]. + + 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: + ~.gcdc_security_settings.SecuritySettings: + Represents the settings related to + security issues, such as data redaction + and data retention. It may take hours + for updates on the settings to propagate + to all the related components and take + effect. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{security_settings.name=projects/*/locations/*/securitySettings/*}", + "body": "security_settings", + }, + ] + request, metadata = self._interceptor.pre_update_security_settings( + request, metadata + ) + pb_request = gcdc_security_settings.UpdateSecuritySettingsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_security_settings.SecuritySettings() + pb_resp = gcdc_security_settings.SecuritySettings.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_security_settings(resp) + return resp + + @property + def create_security_settings( + self, + ) -> Callable[ + [gcdc_security_settings.CreateSecuritySettingsRequest], + gcdc_security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_security_settings( + self, + ) -> Callable[[security_settings.DeleteSecuritySettingsRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_security_settings( + self, + ) -> Callable[ + [security_settings.GetSecuritySettingsRequest], + security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_security_settings( + self, + ) -> Callable[ + [security_settings.ListSecuritySettingsRequest], + security_settings.ListSecuritySettingsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_security_settings( + self, + ) -> Callable[ + [gcdc_security_settings.UpdateSecuritySettingsRequest], + gcdc_security_settings.SecuritySettings, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateSecuritySettings(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SecuritySettingsServiceRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SecuritySettingsServiceRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SecuritySettingsServiceRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/client.py b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/client.py index a00ab80f..99a73f61 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/client.py @@ -58,6 +58,7 @@ from .transports.base import SessionEntityTypesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SessionEntityTypesGrpcTransport from .transports.grpc_asyncio import SessionEntityTypesGrpcAsyncIOTransport +from .transports.rest import SessionEntityTypesRestTransport class SessionEntityTypesClientMeta(type): @@ -73,6 +74,7 @@ class SessionEntityTypesClientMeta(type): ) # type: Dict[str, Type[SessionEntityTypesTransport]] _transport_registry["grpc"] = SessionEntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = SessionEntityTypesGrpcAsyncIOTransport + _transport_registry["rest"] = SessionEntityTypesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/__init__.py index 44e2a8d4..688ff207 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/__init__.py @@ -19,6 +19,8 @@ from .base import SessionEntityTypesTransport from .grpc import SessionEntityTypesGrpcTransport from .grpc_asyncio import SessionEntityTypesGrpcAsyncIOTransport +from .rest import SessionEntityTypesRestTransport +from .rest import SessionEntityTypesRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[SessionEntityTypesTransport]] _transport_registry["grpc"] = SessionEntityTypesGrpcTransport _transport_registry["grpc_asyncio"] = SessionEntityTypesGrpcAsyncIOTransport +_transport_registry["rest"] = SessionEntityTypesRestTransport __all__ = ( "SessionEntityTypesTransport", "SessionEntityTypesGrpcTransport", "SessionEntityTypesGrpcAsyncIOTransport", + "SessionEntityTypesRestTransport", + "SessionEntityTypesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/rest.py new file mode 100644 index 00000000..7d7c2f4c --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/session_entity_types/transports/rest.py @@ -0,0 +1,1390 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import session_entity_type +from google.cloud.dialogflowcx_v3beta1.types import ( + session_entity_type as gcdc_session_entity_type, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + SessionEntityTypesTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SessionEntityTypesRestInterceptor: + """Interceptor for SessionEntityTypes. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SessionEntityTypesRestTransport. + + .. code-block:: python + class MyCustomSessionEntityTypesInterceptor(SessionEntityTypesRestInterceptor): + def pre_create_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_session_entity_types(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_session_entity_types(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_session_entity_type(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_session_entity_type(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SessionEntityTypesRestTransport(interceptor=MyCustomSessionEntityTypesInterceptor()) + client = SessionEntityTypesClient(transport=transport) + + + """ + + def pre_create_session_entity_type( + self, + request: gcdc_session_entity_type.CreateSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_session_entity_type.CreateSessionEntityTypeRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for create_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_create_session_entity_type( + self, response: gcdc_session_entity_type.SessionEntityType + ) -> gcdc_session_entity_type.SessionEntityType: + """Post-rpc interceptor for create_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_delete_session_entity_type( + self, + request: session_entity_type.DeleteSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.DeleteSessionEntityTypeRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for delete_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def pre_get_session_entity_type( + self, + request: session_entity_type.GetSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.GetSessionEntityTypeRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for get_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_session_entity_type( + self, response: session_entity_type.SessionEntityType + ) -> session_entity_type.SessionEntityType: + """Post-rpc interceptor for get_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_session_entity_types( + self, + request: session_entity_type.ListSessionEntityTypesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + session_entity_type.ListSessionEntityTypesRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for list_session_entity_types + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_session_entity_types( + self, response: session_entity_type.ListSessionEntityTypesResponse + ) -> session_entity_type.ListSessionEntityTypesResponse: + """Post-rpc interceptor for list_session_entity_types + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_update_session_entity_type( + self, + request: gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for update_session_entity_type + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_update_session_entity_type( + self, response: gcdc_session_entity_type.SessionEntityType + ) -> gcdc_session_entity_type.SessionEntityType: + """Post-rpc interceptor for update_session_entity_type + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the SessionEntityTypes server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the SessionEntityTypes server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SessionEntityTypesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SessionEntityTypesRestInterceptor + + +class SessionEntityTypesRestTransport(SessionEntityTypesTransport): + """REST backend transport for SessionEntityTypes. + + Service for managing + [SessionEntityTypes][google.cloud.dialogflow.cx.v3beta1.SessionEntityType]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SessionEntityTypesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SessionEntityTypesRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("CreateSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_session_entity_type.CreateSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_session_entity_type.SessionEntityType: + r"""Call the create session entity + type method over HTTP. + + Args: + request (~.gcdc_session_entity_type.CreateSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.CreateSessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.CreateSessionEntityType]. + + 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: + ~.gcdc_session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3beta1.EntityType] at + the user session level (we refer to the entity types + defined at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes", + "body": "session_entity_type", + }, + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*/sessions/*}/entityTypes", + "body": "session_entity_type", + }, + ] + request, metadata = self._interceptor.pre_create_session_entity_type( + request, metadata + ) + pb_request = gcdc_session_entity_type.CreateSessionEntityTypeRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_session_entity_type.SessionEntityType() + pb_resp = gcdc_session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_session_entity_type(resp) + return resp + + class _DeleteSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("DeleteSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.DeleteSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete session entity + type method over HTTP. + + Args: + request (~.session_entity_type.DeleteSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.DeleteSessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.DeleteSessionEntityType]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + }, + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_session_entity_type( + request, metadata + ) + pb_request = session_entity_type.DeleteSessionEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("GetSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.GetSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session_entity_type.SessionEntityType: + r"""Call the get session entity type method over HTTP. + + Args: + request (~.session_entity_type.GetSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.GetSessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.GetSessionEntityType]. + + 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: + ~.session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3beta1.EntityType] at + the user session level (we refer to the entity types + defined at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + }, + ] + request, metadata = self._interceptor.pre_get_session_entity_type( + request, metadata + ) + pb_request = session_entity_type.GetSessionEntityTypeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session_entity_type.SessionEntityType() + pb_resp = session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_session_entity_type(resp) + return resp + + class _ListSessionEntityTypes(SessionEntityTypesRestStub): + def __hash__(self): + return hash("ListSessionEntityTypes") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session_entity_type.ListSessionEntityTypesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session_entity_type.ListSessionEntityTypesResponse: + r"""Call the list session entity types method over HTTP. + + Args: + request (~.session_entity_type.ListSessionEntityTypesRequest): + The request object. The request message for + [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.ListSessionEntityTypes]. + + 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: + ~.session_entity_type.ListSessionEntityTypesResponse: + The response message for + [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.ListSessionEntityTypes]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes", + }, + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*/sessions/*}/entityTypes", + }, + ] + request, metadata = self._interceptor.pre_list_session_entity_types( + request, metadata + ) + pb_request = session_entity_type.ListSessionEntityTypesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session_entity_type.ListSessionEntityTypesResponse() + pb_resp = session_entity_type.ListSessionEntityTypesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_session_entity_types(resp) + return resp + + class _UpdateSessionEntityType(SessionEntityTypesRestStub): + def __hash__(self): + return hash("UpdateSessionEntityType") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_session_entity_type.SessionEntityType: + r"""Call the update session entity + type method over HTTP. + + Args: + request (~.gcdc_session_entity_type.UpdateSessionEntityTypeRequest): + The request object. The request message for + [SessionEntityTypes.UpdateSessionEntityType][google.cloud.dialogflow.cx.v3beta1.SessionEntityTypes.UpdateSessionEntityType]. + + 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: + ~.gcdc_session_entity_type.SessionEntityType: + Session entity types are referred to as **User** entity + types and are entities that are built for an individual + user such as favorites, preferences, playlists, and so + on. + + You can redefine a session entity type at the session + level to extend or replace a [custom entity + type][google.cloud.dialogflow.cx.v3beta1.EntityType] at + the user session level (we refer to the entity types + defined at the agent level as "custom entity types"). + + Note: session entity types apply to all queries, + regardless of the language. + + For more information about entity types, see the + `Dialogflow + documentation `__. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{session_entity_type.name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}", + "body": "session_entity_type", + }, + { + "method": "patch", + "uri": "/v3beta1/{session_entity_type.name=projects/*/locations/*/agents/*/environments/*/sessions/*/entityTypes/*}", + "body": "session_entity_type", + }, + ] + request, metadata = self._interceptor.pre_update_session_entity_type( + request, metadata + ) + pb_request = gcdc_session_entity_type.UpdateSessionEntityTypeRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_session_entity_type.SessionEntityType() + pb_resp = gcdc_session_entity_type.SessionEntityType.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_session_entity_type(resp) + return resp + + @property + def create_session_entity_type( + self, + ) -> Callable[ + [gcdc_session_entity_type.CreateSessionEntityTypeRequest], + gcdc_session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_session_entity_type( + self, + ) -> Callable[ + [session_entity_type.DeleteSessionEntityTypeRequest], empty_pb2.Empty + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_session_entity_type( + self, + ) -> Callable[ + [session_entity_type.GetSessionEntityTypeRequest], + session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_session_entity_types( + self, + ) -> Callable[ + [session_entity_type.ListSessionEntityTypesRequest], + session_entity_type.ListSessionEntityTypesResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListSessionEntityTypes(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_session_entity_type( + self, + ) -> Callable[ + [gcdc_session_entity_type.UpdateSessionEntityTypeRequest], + gcdc_session_entity_type.SessionEntityType, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateSessionEntityType(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SessionEntityTypesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SessionEntityTypesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SessionEntityTypesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SessionEntityTypesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/sessions/client.py b/google/cloud/dialogflowcx_v3beta1/services/sessions/client.py index 69620e54..feff8f51 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/sessions/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/sessions/client.py @@ -56,6 +56,7 @@ from .transports.base import SessionsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SessionsGrpcTransport from .transports.grpc_asyncio import SessionsGrpcAsyncIOTransport +from .transports.rest import SessionsRestTransport class SessionsClientMeta(type): @@ -69,6 +70,7 @@ class SessionsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[SessionsTransport]] _transport_registry["grpc"] = SessionsGrpcTransport _transport_registry["grpc_asyncio"] = SessionsGrpcAsyncIOTransport + _transport_registry["rest"] = SessionsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/__init__.py index ce00ff37..4417beb6 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/__init__.py @@ -19,15 +19,20 @@ from .base import SessionsTransport from .grpc import SessionsGrpcTransport from .grpc_asyncio import SessionsGrpcAsyncIOTransport +from .rest import SessionsRestTransport +from .rest import SessionsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[SessionsTransport]] _transport_registry["grpc"] = SessionsGrpcTransport _transport_registry["grpc_asyncio"] = SessionsGrpcAsyncIOTransport +_transport_registry["rest"] = SessionsRestTransport __all__ = ( "SessionsTransport", "SessionsGrpcTransport", "SessionsGrpcAsyncIOTransport", + "SessionsRestTransport", + "SessionsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/rest.py new file mode 100644 index 00000000..36f1374a --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/sessions/transports/rest.py @@ -0,0 +1,1069 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import session + +from .base import SessionsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SessionsRestInterceptor: + """Interceptor for Sessions. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the SessionsRestTransport. + + .. code-block:: python + class MyCustomSessionsInterceptor(SessionsRestInterceptor): + def pre_detect_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_detect_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_fulfill_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_fulfill_intent(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_match_intent(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_match_intent(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SessionsRestTransport(interceptor=MyCustomSessionsInterceptor()) + client = SessionsClient(transport=transport) + + + """ + + def pre_detect_intent( + self, request: session.DetectIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.DetectIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for detect_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_detect_intent( + self, response: session.DetectIntentResponse + ) -> session.DetectIntentResponse: + """Post-rpc interceptor for detect_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_fulfill_intent( + self, request: session.FulfillIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.FulfillIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for fulfill_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_fulfill_intent( + self, response: session.FulfillIntentResponse + ) -> session.FulfillIntentResponse: + """Post-rpc interceptor for fulfill_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_match_intent( + self, request: session.MatchIntentRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[session.MatchIntentRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for match_intent + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_match_intent( + self, response: session.MatchIntentResponse + ) -> session.MatchIntentResponse: + """Post-rpc interceptor for match_intent + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Sessions server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Sessions server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SessionsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SessionsRestInterceptor + + +class SessionsRestTransport(SessionsTransport): + """REST backend transport for Sessions. + + A session represents an interaction with a user. You retrieve user + input and pass it to the + [DetectIntent][google.cloud.dialogflow.cx.v3beta1.Sessions.DetectIntent] + method to determine user intent and respond. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[SessionsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or SessionsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _DetectIntent(SessionsRestStub): + def __hash__(self): + return hash("DetectIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session.DetectIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.DetectIntentResponse: + r"""Call the detect intent method over HTTP. + + Args: + request (~.session.DetectIntentRequest): + The request object. The request to detect user's intent. + 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: + ~.session.DetectIntentResponse: + The message returned from the + DetectIntent method. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{session=projects/*/locations/*/agents/*/sessions/*}:detectIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3beta1/{session=projects/*/locations/*/agents/*/environments/*/sessions/*}:detectIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_detect_intent(request, metadata) + pb_request = session.DetectIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.DetectIntentResponse() + pb_resp = session.DetectIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_detect_intent(resp) + return resp + + class _FulfillIntent(SessionsRestStub): + def __hash__(self): + return hash("FulfillIntent") + + def __call__( + self, + request: session.FulfillIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.FulfillIntentResponse: + r"""Call the fulfill intent method over HTTP. + + Args: + request (~.session.FulfillIntentRequest): + The request object. Request of [FulfillIntent][] + 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: + ~.session.FulfillIntentResponse: + Response of [FulfillIntent][] + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{match_intent_request.session=projects/*/locations/*/agents/*/sessions/*}:fulfillIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3beta1/{match_intent_request.session=projects/*/locations/*/agents/*/environments/*/sessions/*}:fulfillIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_fulfill_intent(request, metadata) + pb_request = session.FulfillIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.FulfillIntentResponse() + pb_resp = session.FulfillIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_fulfill_intent(resp) + return resp + + class _MatchIntent(SessionsRestStub): + def __hash__(self): + return hash("MatchIntent") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: session.MatchIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> session.MatchIntentResponse: + r"""Call the match intent method over HTTP. + + Args: + request (~.session.MatchIntentRequest): + The request object. Request of [MatchIntent][]. + 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: + ~.session.MatchIntentResponse: + Response of [MatchIntent][]. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{session=projects/*/locations/*/agents/*/sessions/*}:matchIntent", + "body": "*", + }, + { + "method": "post", + "uri": "/v3beta1/{session=projects/*/locations/*/agents/*/environments/*/sessions/*}:matchIntent", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_match_intent(request, metadata) + pb_request = session.MatchIntentRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = session.MatchIntentResponse() + pb_resp = session.MatchIntentResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_match_intent(resp) + return resp + + class _StreamingDetectIntent(SessionsRestStub): + def __hash__(self): + return hash("StreamingDetectIntent") + + def __call__( + self, + request: session.StreamingDetectIntentRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> rest_streaming.ResponseIterator: + raise NotImplementedError( + "Method StreamingDetectIntent is not available over REST transport" + ) + + @property + def detect_intent( + self, + ) -> Callable[[session.DetectIntentRequest], session.DetectIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DetectIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def fulfill_intent( + self, + ) -> Callable[[session.FulfillIntentRequest], session.FulfillIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._FulfillIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def match_intent( + self, + ) -> Callable[[session.MatchIntentRequest], session.MatchIntentResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._MatchIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def streaming_detect_intent( + self, + ) -> Callable[ + [session.StreamingDetectIntentRequest], session.StreamingDetectIntentResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._StreamingDetectIntent(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(SessionsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(SessionsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(SessionsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(SessionsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(SessionsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SessionsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/test_cases/client.py b/google/cloud/dialogflowcx_v3beta1/services/test_cases/client.py index f2b3f6be..c41dcd7e 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/test_cases/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/test_cases/client.py @@ -58,6 +58,7 @@ from .transports.base import TestCasesTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TestCasesGrpcTransport from .transports.grpc_asyncio import TestCasesGrpcAsyncIOTransport +from .transports.rest import TestCasesRestTransport class TestCasesClientMeta(type): @@ -71,6 +72,7 @@ class TestCasesClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[TestCasesTransport]] _transport_registry["grpc"] = TestCasesGrpcTransport _transport_registry["grpc_asyncio"] = TestCasesGrpcAsyncIOTransport + _transport_registry["rest"] = TestCasesRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/__init__.py index 10d2c977..0b296261 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/__init__.py @@ -19,15 +19,20 @@ from .base import TestCasesTransport from .grpc import TestCasesGrpcTransport from .grpc_asyncio import TestCasesGrpcAsyncIOTransport +from .rest import TestCasesRestTransport +from .rest import TestCasesRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[TestCasesTransport]] _transport_registry["grpc"] = TestCasesGrpcTransport _transport_registry["grpc_asyncio"] = TestCasesGrpcAsyncIOTransport +_transport_registry["rest"] = TestCasesRestTransport __all__ = ( "TestCasesTransport", "TestCasesGrpcTransport", "TestCasesGrpcAsyncIOTransport", + "TestCasesRestTransport", + "TestCasesRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/rest.py new file mode 100644 index 00000000..d3fb3b21 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/test_cases/transports/rest.py @@ -0,0 +1,2295 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import test_case +from google.cloud.dialogflowcx_v3beta1.types import test_case as gcdc_test_case +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import TestCasesTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class TestCasesRestInterceptor: + """Interceptor for TestCases. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the TestCasesRestTransport. + + .. code-block:: python + class MyCustomTestCasesInterceptor(TestCasesRestInterceptor): + def pre_batch_delete_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_batch_run_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_batch_run_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_calculate_coverage(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_calculate_coverage(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_export_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_export_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_test_case_result(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_test_case_result(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_import_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_import_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_test_case_results(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_test_case_results(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_test_cases(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_test_cases(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_run_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_run_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_test_case(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_test_case(self, response): + logging.log(f"Received response: {response}") + return response + + transport = TestCasesRestTransport(interceptor=MyCustomTestCasesInterceptor()) + client = TestCasesClient(transport=transport) + + + """ + + def pre_batch_delete_test_cases( + self, + request: test_case.BatchDeleteTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.BatchDeleteTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for batch_delete_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def pre_batch_run_test_cases( + self, + request: test_case.BatchRunTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.BatchRunTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for batch_run_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_batch_run_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for batch_run_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_calculate_coverage( + self, + request: test_case.CalculateCoverageRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.CalculateCoverageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for calculate_coverage + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_calculate_coverage( + self, response: test_case.CalculateCoverageResponse + ) -> test_case.CalculateCoverageResponse: + """Post-rpc interceptor for calculate_coverage + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_create_test_case( + self, + request: gcdc_test_case.CreateTestCaseRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_test_case.CreateTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_create_test_case( + self, response: gcdc_test_case.TestCase + ) -> gcdc_test_case.TestCase: + """Post-rpc interceptor for create_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_export_test_cases( + self, + request: test_case.ExportTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ExportTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for export_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_export_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for export_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_test_case( + self, request: test_case.GetTestCaseRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[test_case.GetTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_test_case(self, response: test_case.TestCase) -> test_case.TestCase: + """Post-rpc interceptor for get_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_test_case_result( + self, + request: test_case.GetTestCaseResultRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.GetTestCaseResultRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_test_case_result + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_test_case_result( + self, response: test_case.TestCaseResult + ) -> test_case.TestCaseResult: + """Post-rpc interceptor for get_test_case_result + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_import_test_cases( + self, + request: test_case.ImportTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ImportTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for import_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_import_test_cases( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for import_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_test_case_results( + self, + request: test_case.ListTestCaseResultsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ListTestCaseResultsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_test_case_results + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_test_case_results( + self, response: test_case.ListTestCaseResultsResponse + ) -> test_case.ListTestCaseResultsResponse: + """Post-rpc interceptor for list_test_case_results + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_test_cases( + self, + request: test_case.ListTestCasesRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[test_case.ListTestCasesRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_test_cases + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_test_cases( + self, response: test_case.ListTestCasesResponse + ) -> test_case.ListTestCasesResponse: + """Post-rpc interceptor for list_test_cases + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_run_test_case( + self, request: test_case.RunTestCaseRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[test_case.RunTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for run_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_run_test_case( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for run_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_update_test_case( + self, + request: gcdc_test_case.UpdateTestCaseRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_test_case.UpdateTestCaseRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_test_case + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_update_test_case( + self, response: gcdc_test_case.TestCase + ) -> gcdc_test_case.TestCase: + """Post-rpc interceptor for update_test_case + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TestCases server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the TestCases server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class TestCasesRestStub: + _session: AuthorizedSession + _host: str + _interceptor: TestCasesRestInterceptor + + +class TestCasesRestTransport(TestCasesTransport): + """REST backend transport for TestCases. + + Service for managing [Test + Cases][google.cloud.dialogflow.cx.v3beta1.TestCase] and [Test Case + Results][google.cloud.dialogflow.cx.v3beta1.TestCaseResult]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[TestCasesRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or TestCasesRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3beta1", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _BatchDeleteTestCases(TestCasesRestStub): + def __hash__(self): + return hash("BatchDeleteTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.BatchDeleteTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the batch delete test cases method over HTTP. + + Args: + request (~.test_case.BatchDeleteTestCasesRequest): + The request object. The request message for + [TestCases.BatchDeleteTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.BatchDeleteTestCases]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases:batchDelete", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_batch_delete_test_cases( + request, metadata + ) + pb_request = test_case.BatchDeleteTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _BatchRunTestCases(TestCasesRestStub): + def __hash__(self): + return hash("BatchRunTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.BatchRunTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the batch run test cases method over HTTP. + + Args: + request (~.test_case.BatchRunTestCasesRequest): + The request object. The request message for + [TestCases.BatchRunTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.BatchRunTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases:batchRun", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_batch_run_test_cases( + request, metadata + ) + pb_request = test_case.BatchRunTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_batch_run_test_cases(resp) + return resp + + class _CalculateCoverage(TestCasesRestStub): + def __hash__(self): + return hash("CalculateCoverage") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "type": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.CalculateCoverageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.CalculateCoverageResponse: + r"""Call the calculate coverage method over HTTP. + + Args: + request (~.test_case.CalculateCoverageRequest): + The request object. The request message for + [TestCases.CalculateCoverage][google.cloud.dialogflow.cx.v3beta1.TestCases.CalculateCoverage]. + + 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: + ~.test_case.CalculateCoverageResponse: + The response message for + [TestCases.CalculateCoverage][google.cloud.dialogflow.cx.v3beta1.TestCases.CalculateCoverage]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{agent=projects/*/locations/*/agents/*}/testCases:calculateCoverage", + }, + ] + request, metadata = self._interceptor.pre_calculate_coverage( + request, metadata + ) + pb_request = test_case.CalculateCoverageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.CalculateCoverageResponse() + pb_resp = test_case.CalculateCoverageResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_calculate_coverage(resp) + return resp + + class _CreateTestCase(TestCasesRestStub): + def __hash__(self): + return hash("CreateTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_test_case.CreateTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_test_case.TestCase: + r"""Call the create test case method over HTTP. + + Args: + request (~.gcdc_test_case.CreateTestCaseRequest): + The request object. The request message for + [TestCases.CreateTestCase][google.cloud.dialogflow.cx.v3beta1.TestCases.CreateTestCase]. + + 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: + ~.gcdc_test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases", + "body": "test_case", + }, + ] + request, metadata = self._interceptor.pre_create_test_case( + request, metadata + ) + pb_request = gcdc_test_case.CreateTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_test_case.TestCase() + pb_resp = gcdc_test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_test_case(resp) + return resp + + class _ExportTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ExportTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ExportTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the export test cases method over HTTP. + + Args: + request (~.test_case.ExportTestCasesRequest): + The request object. The request message for + [TestCases.ExportTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.ExportTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases:export", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_export_test_cases( + request, metadata + ) + pb_request = test_case.ExportTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_export_test_cases(resp) + return resp + + class _GetTestCase(TestCasesRestStub): + def __hash__(self): + return hash("GetTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.GetTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.TestCase: + r"""Call the get test case method over HTTP. + + Args: + request (~.test_case.GetTestCaseRequest): + The request object. The request message for + [TestCases.GetTestCase][google.cloud.dialogflow.cx.v3beta1.TestCases.GetTestCase]. + + 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: + ~.test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/testCases/*}", + }, + ] + request, metadata = self._interceptor.pre_get_test_case(request, metadata) + pb_request = test_case.GetTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.TestCase() + pb_resp = test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_test_case(resp) + return resp + + class _GetTestCaseResult(TestCasesRestStub): + def __hash__(self): + return hash("GetTestCaseResult") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.GetTestCaseResultRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.TestCaseResult: + r"""Call the get test case result method over HTTP. + + Args: + request (~.test_case.GetTestCaseResultRequest): + The request object. The request message for + [TestCases.GetTestCaseResult][google.cloud.dialogflow.cx.v3beta1.TestCases.GetTestCaseResult]. + + 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: + ~.test_case.TestCaseResult: + Represents a result from running a + test case in an agent environment. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/testCases/*/results/*}", + }, + ] + request, metadata = self._interceptor.pre_get_test_case_result( + request, metadata + ) + pb_request = test_case.GetTestCaseResultRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.TestCaseResult() + pb_resp = test_case.TestCaseResult.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_test_case_result(resp) + return resp + + class _ImportTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ImportTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ImportTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the import test cases method over HTTP. + + Args: + request (~.test_case.ImportTestCasesRequest): + The request object. The request message for + [TestCases.ImportTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.ImportTestCases]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases:import", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_import_test_cases( + request, metadata + ) + pb_request = test_case.ImportTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_import_test_cases(resp) + return resp + + class _ListTestCaseResults(TestCasesRestStub): + def __hash__(self): + return hash("ListTestCaseResults") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ListTestCaseResultsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.ListTestCaseResultsResponse: + r"""Call the list test case results method over HTTP. + + Args: + request (~.test_case.ListTestCaseResultsRequest): + The request object. The request message for + [TestCases.ListTestCaseResults][google.cloud.dialogflow.cx.v3beta1.TestCases.ListTestCaseResults]. + + 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: + ~.test_case.ListTestCaseResultsResponse: + The response message for + [TestCases.ListTestCaseResults][google.cloud.dialogflow.cx.v3beta1.TestCases.ListTestCaseResults]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/testCases/*}/results", + }, + ] + request, metadata = self._interceptor.pre_list_test_case_results( + request, metadata + ) + pb_request = test_case.ListTestCaseResultsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.ListTestCaseResultsResponse() + pb_resp = test_case.ListTestCaseResultsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_test_case_results(resp) + return resp + + class _ListTestCases(TestCasesRestStub): + def __hash__(self): + return hash("ListTestCases") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.ListTestCasesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> test_case.ListTestCasesResponse: + r"""Call the list test cases method over HTTP. + + Args: + request (~.test_case.ListTestCasesRequest): + The request object. The request message for + [TestCases.ListTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.ListTestCases]. + + 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: + ~.test_case.ListTestCasesResponse: + The response message for + [TestCases.ListTestCases][google.cloud.dialogflow.cx.v3beta1.TestCases.ListTestCases]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases", + }, + ] + request, metadata = self._interceptor.pre_list_test_cases(request, metadata) + pb_request = test_case.ListTestCasesRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = test_case.ListTestCasesResponse() + pb_resp = test_case.ListTestCasesResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_test_cases(resp) + return resp + + class _RunTestCase(TestCasesRestStub): + def __hash__(self): + return hash("RunTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: test_case.RunTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the run test case method over HTTP. + + Args: + request (~.test_case.RunTestCaseRequest): + The request object. The request message for + [TestCases.RunTestCase][google.cloud.dialogflow.cx.v3beta1.TestCases.RunTestCase]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/testCases/*}:run", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_run_test_case(request, metadata) + pb_request = test_case.RunTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_run_test_case(resp) + return resp + + class _UpdateTestCase(TestCasesRestStub): + def __hash__(self): + return hash("UpdateTestCase") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_test_case.UpdateTestCaseRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_test_case.TestCase: + r"""Call the update test case method over HTTP. + + Args: + request (~.gcdc_test_case.UpdateTestCaseRequest): + The request object. The request message for + [TestCases.UpdateTestCase][google.cloud.dialogflow.cx.v3beta1.TestCases.UpdateTestCase]. + + 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: + ~.gcdc_test_case.TestCase: + Represents a test case. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{test_case.name=projects/*/locations/*/agents/*/testCases/*}", + "body": "test_case", + }, + ] + request, metadata = self._interceptor.pre_update_test_case( + request, metadata + ) + pb_request = gcdc_test_case.UpdateTestCaseRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_test_case.TestCase() + pb_resp = gcdc_test_case.TestCase.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_test_case(resp) + return resp + + @property + def batch_delete_test_cases( + self, + ) -> Callable[[test_case.BatchDeleteTestCasesRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._BatchDeleteTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def batch_run_test_cases( + self, + ) -> Callable[[test_case.BatchRunTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._BatchRunTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def calculate_coverage( + self, + ) -> Callable[ + [test_case.CalculateCoverageRequest], test_case.CalculateCoverageResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CalculateCoverage(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_test_case( + self, + ) -> Callable[[gcdc_test_case.CreateTestCaseRequest], gcdc_test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def export_test_cases( + self, + ) -> Callable[[test_case.ExportTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ExportTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_test_case( + self, + ) -> Callable[[test_case.GetTestCaseRequest], test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_test_case_result( + self, + ) -> Callable[[test_case.GetTestCaseResultRequest], test_case.TestCaseResult]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTestCaseResult(self._session, self._host, self._interceptor) # type: ignore + + @property + def import_test_cases( + self, + ) -> Callable[[test_case.ImportTestCasesRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ImportTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_test_case_results( + self, + ) -> Callable[ + [test_case.ListTestCaseResultsRequest], test_case.ListTestCaseResultsResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTestCaseResults(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_test_cases( + self, + ) -> Callable[[test_case.ListTestCasesRequest], test_case.ListTestCasesResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTestCases(self._session, self._host, self._interceptor) # type: ignore + + @property + def run_test_case( + self, + ) -> Callable[[test_case.RunTestCaseRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._RunTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_test_case( + self, + ) -> Callable[[gcdc_test_case.UpdateTestCaseRequest], gcdc_test_case.TestCase]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateTestCase(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(TestCasesRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(TestCasesRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(TestCasesRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("TestCasesRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/client.py b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/client.py index 6cd5d6a9..6fb63bd4 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/client.py @@ -58,6 +58,7 @@ from .transports.base import TransitionRouteGroupsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TransitionRouteGroupsGrpcTransport from .transports.grpc_asyncio import TransitionRouteGroupsGrpcAsyncIOTransport +from .transports.rest import TransitionRouteGroupsRestTransport class TransitionRouteGroupsClientMeta(type): @@ -73,6 +74,7 @@ class TransitionRouteGroupsClientMeta(type): ) # type: Dict[str, Type[TransitionRouteGroupsTransport]] _transport_registry["grpc"] = TransitionRouteGroupsGrpcTransport _transport_registry["grpc_asyncio"] = TransitionRouteGroupsGrpcAsyncIOTransport + _transport_registry["rest"] = TransitionRouteGroupsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/__init__.py index ed445782..a9ee37b8 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/__init__.py @@ -19,6 +19,8 @@ from .base import TransitionRouteGroupsTransport from .grpc import TransitionRouteGroupsGrpcTransport from .grpc_asyncio import TransitionRouteGroupsGrpcAsyncIOTransport +from .rest import TransitionRouteGroupsRestTransport +from .rest import TransitionRouteGroupsRestInterceptor # Compile a registry of transports. @@ -27,9 +29,12 @@ ) # type: Dict[str, Type[TransitionRouteGroupsTransport]] _transport_registry["grpc"] = TransitionRouteGroupsGrpcTransport _transport_registry["grpc_asyncio"] = TransitionRouteGroupsGrpcAsyncIOTransport +_transport_registry["rest"] = TransitionRouteGroupsRestTransport __all__ = ( "TransitionRouteGroupsTransport", "TransitionRouteGroupsGrpcTransport", "TransitionRouteGroupsGrpcAsyncIOTransport", + "TransitionRouteGroupsRestTransport", + "TransitionRouteGroupsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/rest.py new file mode 100644 index 00000000..126deb54 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/transition_route_groups/transports/rest.py @@ -0,0 +1,1343 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import transition_route_group +from google.cloud.dialogflowcx_v3beta1.types import ( + transition_route_group as gcdc_transition_route_group, +) +from google.protobuf import empty_pb2 # type: ignore + +from .base import ( + TransitionRouteGroupsTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class TransitionRouteGroupsRestInterceptor: + """Interceptor for TransitionRouteGroups. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the TransitionRouteGroupsRestTransport. + + .. code-block:: python + class MyCustomTransitionRouteGroupsInterceptor(TransitionRouteGroupsRestInterceptor): + def pre_create_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_transition_route_groups(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_transition_route_groups(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_transition_route_group(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_transition_route_group(self, response): + logging.log(f"Received response: {response}") + return response + + transport = TransitionRouteGroupsRestTransport(interceptor=MyCustomTransitionRouteGroupsInterceptor()) + client = TransitionRouteGroupsClient(transport=transport) + + + """ + + def pre_create_transition_route_group( + self, + request: gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for create_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_create_transition_route_group( + self, response: gcdc_transition_route_group.TransitionRouteGroup + ) -> gcdc_transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for create_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_delete_transition_route_group( + self, + request: transition_route_group.DeleteTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.DeleteTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for delete_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def pre_get_transition_route_group( + self, + request: transition_route_group.GetTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.GetTransitionRouteGroupRequest, Sequence[Tuple[str, str]] + ]: + """Pre-rpc interceptor for get_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_transition_route_group( + self, response: transition_route_group.TransitionRouteGroup + ) -> transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for get_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_transition_route_groups( + self, + request: transition_route_group.ListTransitionRouteGroupsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + transition_route_group.ListTransitionRouteGroupsRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for list_transition_route_groups + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_transition_route_groups( + self, response: transition_route_group.ListTransitionRouteGroupsResponse + ) -> transition_route_group.ListTransitionRouteGroupsResponse: + """Post-rpc interceptor for list_transition_route_groups + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_update_transition_route_group( + self, + request: gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[ + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + Sequence[Tuple[str, str]], + ]: + """Pre-rpc interceptor for update_transition_route_group + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_update_transition_route_group( + self, response: gcdc_transition_route_group.TransitionRouteGroup + ) -> gcdc_transition_route_group.TransitionRouteGroup: + """Post-rpc interceptor for update_transition_route_group + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the TransitionRouteGroups server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the TransitionRouteGroups server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class TransitionRouteGroupsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: TransitionRouteGroupsRestInterceptor + + +class TransitionRouteGroupsRestTransport(TransitionRouteGroupsTransport): + """REST backend transport for TransitionRouteGroups. + + Service for managing + [TransitionRouteGroups][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroup]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[TransitionRouteGroupsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or TransitionRouteGroupsRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("CreateTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_transition_route_group.TransitionRouteGroup: + r"""Call the create transition route + group method over HTTP. + + Args: + request (~.gcdc_transition_route_group.CreateTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.CreateTransitionRouteGroup][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.CreateTransitionRouteGroup]. + + 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: + ~.gcdc_transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3beta1.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3beta1.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups", + "body": "transition_route_group", + }, + ] + request, metadata = self._interceptor.pre_create_transition_route_group( + request, metadata + ) + pb_request = ( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest.pb( + request + ) + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_transition_route_group.TransitionRouteGroup() + pb_resp = gcdc_transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_transition_route_group(resp) + return resp + + class _DeleteTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("DeleteTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.DeleteTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete transition route + group method over HTTP. + + Args: + request (~.transition_route_group.DeleteTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.DeleteTransitionRouteGroup][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.DeleteTransitionRouteGroup]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_transition_route_group( + request, metadata + ) + pb_request = transition_route_group.DeleteTransitionRouteGroupRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("GetTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.GetTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> transition_route_group.TransitionRouteGroup: + r"""Call the get transition route + group method over HTTP. + + Args: + request (~.transition_route_group.GetTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.GetTransitionRouteGroup][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.GetTransitionRouteGroup]. + + 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: + ~.transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3beta1.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3beta1.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + }, + ] + request, metadata = self._interceptor.pre_get_transition_route_group( + request, metadata + ) + pb_request = transition_route_group.GetTransitionRouteGroupRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = transition_route_group.TransitionRouteGroup() + pb_resp = transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_transition_route_group(resp) + return resp + + class _ListTransitionRouteGroups(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("ListTransitionRouteGroups") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: transition_route_group.ListTransitionRouteGroupsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> transition_route_group.ListTransitionRouteGroupsResponse: + r"""Call the list transition route + groups method over HTTP. + + Args: + request (~.transition_route_group.ListTransitionRouteGroupsRequest): + The request object. The request message for + [TransitionRouteGroups.ListTransitionRouteGroups][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.ListTransitionRouteGroups]. + + 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: + ~.transition_route_group.ListTransitionRouteGroupsResponse: + The response message for + [TransitionRouteGroups.ListTransitionRouteGroups][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.ListTransitionRouteGroups]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups", + }, + ] + request, metadata = self._interceptor.pre_list_transition_route_groups( + request, metadata + ) + pb_request = transition_route_group.ListTransitionRouteGroupsRequest.pb( + request + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = transition_route_group.ListTransitionRouteGroupsResponse() + pb_resp = transition_route_group.ListTransitionRouteGroupsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_transition_route_groups(resp) + return resp + + class _UpdateTransitionRouteGroup(TransitionRouteGroupsRestStub): + def __hash__(self): + return hash("UpdateTransitionRouteGroup") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_transition_route_group.TransitionRouteGroup: + r"""Call the update transition route + group method over HTTP. + + Args: + request (~.gcdc_transition_route_group.UpdateTransitionRouteGroupRequest): + The request object. The request message for + [TransitionRouteGroups.UpdateTransitionRouteGroup][google.cloud.dialogflow.cx.v3beta1.TransitionRouteGroups.UpdateTransitionRouteGroup]. + + 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: + ~.gcdc_transition_route_group.TransitionRouteGroup: + An TransitionRouteGroup represents a group of + [``TransitionRoutes``][google.cloud.dialogflow.cx.v3beta1.TransitionRoute] + to be used by a + [Page][google.cloud.dialogflow.cx.v3beta1.Page]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{transition_route_group.name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}", + "body": "transition_route_group", + }, + ] + request, metadata = self._interceptor.pre_update_transition_route_group( + request, metadata + ) + pb_request = ( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest.pb( + request + ) + ) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_transition_route_group.TransitionRouteGroup() + pb_resp = gcdc_transition_route_group.TransitionRouteGroup.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_transition_route_group(resp) + return resp + + @property + def create_transition_route_group( + self, + ) -> Callable[ + [gcdc_transition_route_group.CreateTransitionRouteGroupRequest], + gcdc_transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_transition_route_group( + self, + ) -> Callable[ + [transition_route_group.DeleteTransitionRouteGroupRequest], empty_pb2.Empty + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_transition_route_group( + self, + ) -> Callable[ + [transition_route_group.GetTransitionRouteGroupRequest], + transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_transition_route_groups( + self, + ) -> Callable[ + [transition_route_group.ListTransitionRouteGroupsRequest], + transition_route_group.ListTransitionRouteGroupsResponse, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListTransitionRouteGroups(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_transition_route_group( + self, + ) -> Callable[ + [gcdc_transition_route_group.UpdateTransitionRouteGroupRequest], + gcdc_transition_route_group.TransitionRouteGroup, + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateTransitionRouteGroup(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(TransitionRouteGroupsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(TransitionRouteGroupsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("TransitionRouteGroupsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/versions/client.py b/google/cloud/dialogflowcx_v3beta1/services/versions/client.py index 826fe724..624cff68 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/versions/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/versions/client.py @@ -61,6 +61,7 @@ from .transports.base import VersionsTransport, DEFAULT_CLIENT_INFO from .transports.grpc import VersionsGrpcTransport from .transports.grpc_asyncio import VersionsGrpcAsyncIOTransport +from .transports.rest import VersionsRestTransport class VersionsClientMeta(type): @@ -74,6 +75,7 @@ class VersionsClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[VersionsTransport]] _transport_registry["grpc"] = VersionsGrpcTransport _transport_registry["grpc_asyncio"] = VersionsGrpcAsyncIOTransport + _transport_registry["rest"] = VersionsRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/versions/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/versions/transports/__init__.py index ab80b3b5..780f6ec5 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/versions/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/versions/transports/__init__.py @@ -19,15 +19,20 @@ from .base import VersionsTransport from .grpc import VersionsGrpcTransport from .grpc_asyncio import VersionsGrpcAsyncIOTransport +from .rest import VersionsRestTransport +from .rest import VersionsRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[VersionsTransport]] _transport_registry["grpc"] = VersionsGrpcTransport _transport_registry["grpc_asyncio"] = VersionsGrpcAsyncIOTransport +_transport_registry["rest"] = VersionsRestTransport __all__ = ( "VersionsTransport", "VersionsGrpcTransport", "VersionsGrpcAsyncIOTransport", + "VersionsRestTransport", + "VersionsRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/versions/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/versions/transports/rest.py new file mode 100644 index 00000000..b53fc8c0 --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/versions/transports/rest.py @@ -0,0 +1,1598 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.api_core import operations_v1 +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import version +from google.cloud.dialogflowcx_v3beta1.types import version as gcdc_version +from google.longrunning import operations_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore + +from .base import VersionsTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class VersionsRestInterceptor: + """Interceptor for Versions. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the VersionsRestTransport. + + .. code-block:: python + class MyCustomVersionsInterceptor(VersionsRestInterceptor): + def pre_compare_versions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_compare_versions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_versions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_versions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_load_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_load_version(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_version(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_version(self, response): + logging.log(f"Received response: {response}") + return response + + transport = VersionsRestTransport(interceptor=MyCustomVersionsInterceptor()) + client = VersionsClient(transport=transport) + + + """ + + def pre_compare_versions( + self, + request: version.CompareVersionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[version.CompareVersionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for compare_versions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_compare_versions( + self, response: version.CompareVersionsResponse + ) -> version.CompareVersionsResponse: + """Post-rpc interceptor for compare_versions + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_create_version( + self, + request: gcdc_version.CreateVersionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_version.CreateVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_create_version( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for create_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_delete_version( + self, request: version.DeleteVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.DeleteVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def pre_get_version( + self, request: version.GetVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.GetVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_version(self, response: version.Version) -> version.Version: + """Post-rpc interceptor for get_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_versions( + self, request: version.ListVersionsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.ListVersionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_versions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_versions( + self, response: version.ListVersionsResponse + ) -> version.ListVersionsResponse: + """Post-rpc interceptor for list_versions + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_load_version( + self, request: version.LoadVersionRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[version.LoadVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for load_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_load_version( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for load_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_update_version( + self, + request: gcdc_version.UpdateVersionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_version.UpdateVersionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_version + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_update_version( + self, response: gcdc_version.Version + ) -> gcdc_version.Version: + """Post-rpc interceptor for update_version + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Versions server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Versions server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class VersionsRestStub: + _session: AuthorizedSession + _host: str + _interceptor: VersionsRestInterceptor + + +class VersionsRestTransport(VersionsTransport): + """REST backend transport for Versions. + + Service for managing + [Versions][google.cloud.dialogflow.cx.v3beta1.Version]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[VersionsRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + self._operations_client: Optional[operations_v1.AbstractOperationsClient] = None + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or VersionsRestInterceptor() + self._prep_wrapped_messages(client_info) + + @property + def operations_client(self) -> operations_v1.AbstractOperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Only create a new client if we do not already have one. + if self._operations_client is None: + http_options: Dict[str, List[Dict[str, str]]] = { + "google.longrunning.Operations.CancelOperation": [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ], + "google.longrunning.Operations.GetOperation": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ], + "google.longrunning.Operations.ListOperations": [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ], + } + + rest_transport = operations_v1.OperationsRestTransport( + host=self._host, + # use the credentials which are saved + credentials=self._credentials, + scopes=self._scopes, + http_options=http_options, + path_prefix="v3beta1", + ) + + self._operations_client = operations_v1.AbstractOperationsClient( + transport=rest_transport + ) + + # Return the client from cache. + return self._operations_client + + class _CompareVersions(VersionsRestStub): + def __hash__(self): + return hash("CompareVersions") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.CompareVersionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.CompareVersionsResponse: + r"""Call the compare versions method over HTTP. + + Args: + request (~.version.CompareVersionsRequest): + The request object. The request message for + [Versions.CompareVersions][google.cloud.dialogflow.cx.v3beta1.Versions.CompareVersions]. + + 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: + ~.version.CompareVersionsResponse: + The response message for + [Versions.CompareVersions][google.cloud.dialogflow.cx.v3beta1.Versions.CompareVersions]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{base_version=projects/*/locations/*/agents/*/flows/*/versions/*}:compareVersions", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_compare_versions( + request, metadata + ) + pb_request = version.CompareVersionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.CompareVersionsResponse() + pb_resp = version.CompareVersionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_compare_versions(resp) + return resp + + class _CreateVersion(VersionsRestStub): + def __hash__(self): + return hash("CreateVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_version.CreateVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the create version method over HTTP. + + Args: + request (~.gcdc_version.CreateVersionRequest): + The request object. The request message for + [Versions.CreateVersion][google.cloud.dialogflow.cx.v3beta1.Versions.CreateVersion]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/versions", + "body": "version", + }, + ] + request, metadata = self._interceptor.pre_create_version(request, metadata) + pb_request = gcdc_version.CreateVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_version(resp) + return resp + + class _DeleteVersion(VersionsRestStub): + def __hash__(self): + return hash("DeleteVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.DeleteVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete version method over HTTP. + + Args: + request (~.version.DeleteVersionRequest): + The request object. The request message for + [Versions.DeleteVersion][google.cloud.dialogflow.cx.v3beta1.Versions.DeleteVersion]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_version(request, metadata) + pb_request = version.DeleteVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetVersion(VersionsRestStub): + def __hash__(self): + return hash("GetVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.GetVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.Version: + r"""Call the get version method over HTTP. + + Args: + request (~.version.GetVersionRequest): + The request object. The request message for + [Versions.GetVersion][google.cloud.dialogflow.cx.v3beta1.Versions.GetVersion]. + + 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: + ~.version.Version: + Represents a version of a flow. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}", + }, + ] + request, metadata = self._interceptor.pre_get_version(request, metadata) + pb_request = version.GetVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.Version() + pb_resp = version.Version.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_version(resp) + return resp + + class _ListVersions(VersionsRestStub): + def __hash__(self): + return hash("ListVersions") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.ListVersionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> version.ListVersionsResponse: + r"""Call the list versions method over HTTP. + + Args: + request (~.version.ListVersionsRequest): + The request object. The request message for + [Versions.ListVersions][google.cloud.dialogflow.cx.v3beta1.Versions.ListVersions]. + + 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: + ~.version.ListVersionsResponse: + The response message for + [Versions.ListVersions][google.cloud.dialogflow.cx.v3beta1.Versions.ListVersions]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/versions", + }, + ] + request, metadata = self._interceptor.pre_list_versions(request, metadata) + pb_request = version.ListVersionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = version.ListVersionsResponse() + pb_resp = version.ListVersionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_versions(resp) + return resp + + class _LoadVersion(VersionsRestStub): + def __hash__(self): + return hash("LoadVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: version.LoadVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + r"""Call the load version method over HTTP. + + Args: + request (~.version.LoadVersionRequest): + The request object. The request message for + [Versions.LoadVersion][google.cloud.dialogflow.cx.v3beta1.Versions.LoadVersion]. + + 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: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}:load", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_load_version(request, metadata) + pb_request = version.LoadVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + resp = self._interceptor.post_load_version(resp) + return resp + + class _UpdateVersion(VersionsRestStub): + def __hash__(self): + return hash("UpdateVersion") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "updateMask": {}, + } + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_version.UpdateVersionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_version.Version: + r"""Call the update version method over HTTP. + + Args: + request (~.gcdc_version.UpdateVersionRequest): + The request object. The request message for + [Versions.UpdateVersion][google.cloud.dialogflow.cx.v3beta1.Versions.UpdateVersion]. + + 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: + ~.gcdc_version.Version: + Represents a version of a flow. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{version.name=projects/*/locations/*/agents/*/flows/*/versions/*}", + "body": "version", + }, + ] + request, metadata = self._interceptor.pre_update_version(request, metadata) + pb_request = gcdc_version.UpdateVersionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_version.Version() + pb_resp = gcdc_version.Version.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_version(resp) + return resp + + @property + def compare_versions( + self, + ) -> Callable[[version.CompareVersionsRequest], version.CompareVersionsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CompareVersions(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_version( + self, + ) -> Callable[[gcdc_version.CreateVersionRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_version( + self, + ) -> Callable[[version.DeleteVersionRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_version(self) -> Callable[[version.GetVersionRequest], version.Version]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_versions( + self, + ) -> Callable[[version.ListVersionsRequest], version.ListVersionsResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListVersions(self._session, self._host, self._interceptor) # type: ignore + + @property + def load_version( + self, + ) -> Callable[[version.LoadVersionRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._LoadVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_version( + self, + ) -> Callable[[gcdc_version.UpdateVersionRequest], gcdc_version.Version]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateVersion(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(VersionsRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(VersionsRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(VersionsRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(VersionsRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(VersionsRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("VersionsRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/services/webhooks/client.py b/google/cloud/dialogflowcx_v3beta1/services/webhooks/client.py index 0e57fba6..ff926482 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/webhooks/client.py +++ b/google/cloud/dialogflowcx_v3beta1/services/webhooks/client.py @@ -56,6 +56,7 @@ from .transports.base import WebhooksTransport, DEFAULT_CLIENT_INFO from .transports.grpc import WebhooksGrpcTransport from .transports.grpc_asyncio import WebhooksGrpcAsyncIOTransport +from .transports.rest import WebhooksRestTransport class WebhooksClientMeta(type): @@ -69,6 +70,7 @@ class WebhooksClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[WebhooksTransport]] _transport_registry["grpc"] = WebhooksGrpcTransport _transport_registry["grpc_asyncio"] = WebhooksGrpcAsyncIOTransport + _transport_registry["rest"] = WebhooksRestTransport def get_transport_class( cls, diff --git a/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/__init__.py b/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/__init__.py index 2beebd10..bd4c8fb7 100644 --- a/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/__init__.py @@ -19,15 +19,20 @@ from .base import WebhooksTransport from .grpc import WebhooksGrpcTransport from .grpc_asyncio import WebhooksGrpcAsyncIOTransport +from .rest import WebhooksRestTransport +from .rest import WebhooksRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[WebhooksTransport]] _transport_registry["grpc"] = WebhooksGrpcTransport _transport_registry["grpc_asyncio"] = WebhooksGrpcAsyncIOTransport +_transport_registry["rest"] = WebhooksRestTransport __all__ = ( "WebhooksTransport", "WebhooksGrpcTransport", "WebhooksGrpcAsyncIOTransport", + "WebhooksRestTransport", + "WebhooksRestInterceptor", ) diff --git a/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/rest.py b/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/rest.py new file mode 100644 index 00000000..232407bf --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/services/webhooks/transports/rest.py @@ -0,0 +1,1280 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.api_core import rest_helpers +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.cloud.location import locations_pb2 # type: ignore +from google.longrunning import operations_pb2 +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.cloud.dialogflowcx_v3beta1.types import webhook +from google.cloud.dialogflowcx_v3beta1.types import webhook as gcdc_webhook +from google.protobuf import empty_pb2 # type: ignore + +from .base import WebhooksTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class WebhooksRestInterceptor: + """Interceptor for Webhooks. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the WebhooksRestTransport. + + .. code-block:: python + class MyCustomWebhooksInterceptor(WebhooksRestInterceptor): + def pre_create_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_webhooks(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_webhooks(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_webhook(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_webhook(self, response): + logging.log(f"Received response: {response}") + return response + + transport = WebhooksRestTransport(interceptor=MyCustomWebhooksInterceptor()) + client = WebhooksClient(transport=transport) + + + """ + + def pre_create_webhook( + self, + request: gcdc_webhook.CreateWebhookRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_webhook.CreateWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_create_webhook( + self, response: gcdc_webhook.Webhook + ) -> gcdc_webhook.Webhook: + """Post-rpc interceptor for create_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_delete_webhook( + self, request: webhook.DeleteWebhookRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.DeleteWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def pre_get_webhook( + self, request: webhook.GetWebhookRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.GetWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_webhook(self, response: webhook.Webhook) -> webhook.Webhook: + """Post-rpc interceptor for get_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_webhooks( + self, request: webhook.ListWebhooksRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[webhook.ListWebhooksRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_webhooks + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_webhooks( + self, response: webhook.ListWebhooksResponse + ) -> webhook.ListWebhooksResponse: + """Post-rpc interceptor for list_webhooks + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_update_webhook( + self, + request: gcdc_webhook.UpdateWebhookRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gcdc_webhook.UpdateWebhookRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_webhook + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_update_webhook( + self, response: gcdc_webhook.Webhook + ) -> gcdc_webhook.Webhook: + """Post-rpc interceptor for update_webhook + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_get_location( + self, + request: locations_pb2.GetLocationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.GetLocationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_location + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_location( + self, response: locations_pb2.Location + ) -> locations_pb2.Location: + """Post-rpc interceptor for get_location + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_locations( + self, + request: locations_pb2.ListLocationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[locations_pb2.ListLocationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_locations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_locations( + self, response: locations_pb2.ListLocationsResponse + ) -> locations_pb2.ListLocationsResponse: + """Post-rpc interceptor for list_locations + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_cancel_operation( + self, + request: operations_pb2.CancelOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.CancelOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_cancel_operation(self, response: None) -> None: + """Post-rpc interceptor for cancel_operation + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_get_operation( + self, + request: operations_pb2.GetOperationRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.GetOperationRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_operation + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_get_operation( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for get_operation + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + def pre_list_operations( + self, + request: operations_pb2.ListOperationsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[operations_pb2.ListOperationsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_operations + + Override in a subclass to manipulate the request or metadata + before they are sent to the Webhooks server. + """ + return request, metadata + + def post_list_operations( + self, response: operations_pb2.ListOperationsResponse + ) -> operations_pb2.ListOperationsResponse: + """Post-rpc interceptor for list_operations + + Override in a subclass to manipulate the response + after it is returned by the Webhooks server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class WebhooksRestStub: + _session: AuthorizedSession + _host: str + _interceptor: WebhooksRestInterceptor + + +class WebhooksRestTransport(WebhooksTransport): + """REST backend transport for Webhooks. + + Service for managing + [Webhooks][google.cloud.dialogflow.cx.v3beta1.Webhook]. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + + """ + + def __init__( + self, + *, + host: str = "dialogflow.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[WebhooksRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` 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): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or WebhooksRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateWebhook(WebhooksRestStub): + def __hash__(self): + return hash("CreateWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_webhook.CreateWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_webhook.Webhook: + r"""Call the create webhook method over HTTP. + + Args: + request (~.gcdc_webhook.CreateWebhookRequest): + The request object. The request message for + [Webhooks.CreateWebhook][google.cloud.dialogflow.cx.v3beta1.Webhooks.CreateWebhook]. + + 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: + ~.gcdc_webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/webhooks", + "body": "webhook", + }, + ] + request, metadata = self._interceptor.pre_create_webhook(request, metadata) + pb_request = gcdc_webhook.CreateWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_webhook.Webhook() + pb_resp = gcdc_webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_webhook(resp) + return resp + + class _DeleteWebhook(WebhooksRestStub): + def __hash__(self): + return hash("DeleteWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.DeleteWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete webhook method over HTTP. + + Args: + request (~.webhook.DeleteWebhookRequest): + The request object. The request message for + [Webhooks.DeleteWebhook][google.cloud.dialogflow.cx.v3beta1.Webhooks.DeleteWebhook]. + + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/webhooks/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_webhook(request, metadata) + pb_request = webhook.DeleteWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetWebhook(WebhooksRestStub): + def __hash__(self): + return hash("GetWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.GetWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> webhook.Webhook: + r"""Call the get webhook method over HTTP. + + Args: + request (~.webhook.GetWebhookRequest): + The request object. The request message for + [Webhooks.GetWebhook][google.cloud.dialogflow.cx.v3beta1.Webhooks.GetWebhook]. + + 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: + ~.webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/agents/*/webhooks/*}", + }, + ] + request, metadata = self._interceptor.pre_get_webhook(request, metadata) + pb_request = webhook.GetWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = webhook.Webhook() + pb_resp = webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_webhook(resp) + return resp + + class _ListWebhooks(WebhooksRestStub): + def __hash__(self): + return hash("ListWebhooks") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: webhook.ListWebhooksRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> webhook.ListWebhooksResponse: + r"""Call the list webhooks method over HTTP. + + Args: + request (~.webhook.ListWebhooksRequest): + The request object. The request message for + [Webhooks.ListWebhooks][google.cloud.dialogflow.cx.v3beta1.Webhooks.ListWebhooks]. + + 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: + ~.webhook.ListWebhooksResponse: + The response message for + [Webhooks.ListWebhooks][google.cloud.dialogflow.cx.v3beta1.Webhooks.ListWebhooks]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{parent=projects/*/locations/*/agents/*}/webhooks", + }, + ] + request, metadata = self._interceptor.pre_list_webhooks(request, metadata) + pb_request = webhook.ListWebhooksRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = webhook.ListWebhooksResponse() + pb_resp = webhook.ListWebhooksResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_webhooks(resp) + return resp + + class _UpdateWebhook(WebhooksRestStub): + def __hash__(self): + return hash("UpdateWebhook") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: gcdc_webhook.UpdateWebhookRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gcdc_webhook.Webhook: + r"""Call the update webhook method over HTTP. + + Args: + request (~.gcdc_webhook.UpdateWebhookRequest): + The request object. The request message for + [Webhooks.UpdateWebhook][google.cloud.dialogflow.cx.v3beta1.Webhooks.UpdateWebhook]. + + 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: + ~.gcdc_webhook.Webhook: + Webhooks host the developer's + business logic. During a session, + webhooks allow the developer to use the + data extracted by Dialogflow's natural + language processing to generate dynamic + responses, validate collected data, or + trigger actions on the backend. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v3beta1/{webhook.name=projects/*/locations/*/agents/*/webhooks/*}", + "body": "webhook", + }, + ] + request, metadata = self._interceptor.pre_update_webhook(request, metadata) + pb_request = gcdc_webhook.UpdateWebhookRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = gcdc_webhook.Webhook() + pb_resp = gcdc_webhook.Webhook.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_webhook(resp) + return resp + + @property + def create_webhook( + self, + ) -> Callable[[gcdc_webhook.CreateWebhookRequest], gcdc_webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._CreateWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_webhook( + self, + ) -> Callable[[webhook.DeleteWebhookRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_webhook(self) -> Callable[[webhook.GetWebhookRequest], webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._GetWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_webhooks( + self, + ) -> Callable[[webhook.ListWebhooksRequest], webhook.ListWebhooksResponse]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ListWebhooks(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_webhook( + self, + ) -> Callable[[gcdc_webhook.UpdateWebhookRequest], gcdc_webhook.Webhook]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateWebhook(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_location(self): + return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore + + class _GetLocation(WebhooksRestStub): + def __call__( + self, + request: locations_pb2.GetLocationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.Location: + + r"""Call the get location method over HTTP. + + Args: + request (locations_pb2.GetLocationRequest): + The request object for GetLocation method. + 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: + locations_pb2.Location: Response from GetLocation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_location(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.Location() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_location(resp) + return resp + + @property + def list_locations(self): + return self._ListLocations(self._session, self._host, self._interceptor) # type: ignore + + class _ListLocations(WebhooksRestStub): + def __call__( + self, + request: locations_pb2.ListLocationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> locations_pb2.ListLocationsResponse: + + r"""Call the list locations method over HTTP. + + Args: + request (locations_pb2.ListLocationsRequest): + The request object for ListLocations method. + 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: + locations_pb2.ListLocationsResponse: Response from ListLocations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/locations", + }, + ] + + request, metadata = self._interceptor.pre_list_locations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = locations_pb2.ListLocationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_locations(resp) + return resp + + @property + def cancel_operation(self): + return self._CancelOperation(self._session, self._host, self._interceptor) # type: ignore + + class _CancelOperation(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.CancelOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + + r"""Call the cancel operation method over HTTP. + + Args: + request (operations_pb2.CancelOperationRequest): + The request object for CancelOperation method. + 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. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/operations/*}:cancel", + }, + { + "method": "post", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}:cancel", + }, + ] + + request, metadata = self._interceptor.pre_cancel_operation( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + return self._interceptor.post_cancel_operation(None) + + @property + def get_operation(self): + return self._GetOperation(self._session, self._host, self._interceptor) # type: ignore + + class _GetOperation(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.GetOperationRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.Operation: + + r"""Call the get operation method over HTTP. + + Args: + request (operations_pb2.GetOperationRequest): + The request object for GetOperation method. + 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: + operations_pb2.Operation: Response from GetOperation method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/operations/*}", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*/operations/*}", + }, + ] + + request, metadata = self._interceptor.pre_get_operation(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.Operation() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_operation(resp) + return resp + + @property + def list_operations(self): + return self._ListOperations(self._session, self._host, self._interceptor) # type: ignore + + class _ListOperations(WebhooksRestStub): + def __call__( + self, + request: operations_pb2.ListOperationsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operations_pb2.ListOperationsResponse: + + r"""Call the list operations method over HTTP. + + Args: + request (operations_pb2.ListOperationsRequest): + The request object for ListOperations method. + 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: + operations_pb2.ListOperationsResponse: Response from ListOperations method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v3beta1/{name=projects/*}/operations", + }, + { + "method": "get", + "uri": "/v3beta1/{name=projects/*/locations/*}/operations", + }, + ] + + request, metadata = self._interceptor.pre_list_operations(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = operations_pb2.ListOperationsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_list_operations(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("WebhooksRestTransport",) diff --git a/google/cloud/dialogflowcx_v3beta1/types/__init__.py b/google/cloud/dialogflowcx_v3beta1/types/__init__.py index fe4a75ea..6f53c3e8 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/__init__.py +++ b/google/cloud/dialogflowcx_v3beta1/types/__init__.py @@ -37,6 +37,7 @@ OutputAudioConfig, SpeechWordInfo, SynthesizeSpeechConfig, + TextToSpeechSettings, VoiceSelectionParams, AudioEncoding, OutputAudioEncoding, @@ -120,6 +121,9 @@ from .fulfillment import ( Fulfillment, ) +from .gcs import ( + GcsDestination, +) from .intent import ( CreateIntentRequest, DeleteIntentRequest, @@ -280,6 +284,7 @@ "OutputAudioConfig", "SpeechWordInfo", "SynthesizeSpeechConfig", + "TextToSpeechSettings", "VoiceSelectionParams", "AudioEncoding", "OutputAudioEncoding", @@ -348,6 +353,7 @@ "UpdateFlowRequest", "ValidateFlowRequest", "Fulfillment", + "GcsDestination", "CreateIntentRequest", "DeleteIntentRequest", "GetIntentRequest", diff --git a/google/cloud/dialogflowcx_v3beta1/types/advanced_settings.py b/google/cloud/dialogflowcx_v3beta1/types/advanced_settings.py index 03921ec2..2b1c3403 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/advanced_settings.py +++ b/google/cloud/dialogflowcx_v3beta1/types/advanced_settings.py @@ -13,10 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore +from google.cloud.dialogflowcx_v3beta1.types import gcs + __protobuf__ = proto.module( package="google.cloud.dialogflow.cx.v3beta1", @@ -41,6 +45,13 @@ class AdvancedSettings(proto.Message): Hierarchy: Agent->Flow->Page->Fulfillment/Parameter. Attributes: + audio_export_gcs_destination (google.cloud.dialogflowcx_v3beta1.types.GcsDestination): + If present, incoming audio is exported by + Dialogflow to the configured Google Cloud + Storage destination. Exposed at the following + levels: + - Agent level + - Flow level logging_settings (google.cloud.dialogflowcx_v3beta1.types.AdvancedSettings.LoggingSettings): Settings for logging. Settings for Dialogflow History, Contact Center @@ -70,6 +81,11 @@ class LoggingSettings(proto.Message): number=3, ) + audio_export_gcs_destination: gcs.GcsDestination = proto.Field( + proto.MESSAGE, + number=2, + message=gcs.GcsDestination, + ) logging_settings: LoggingSettings = proto.Field( proto.MESSAGE, number=6, diff --git a/google/cloud/dialogflowcx_v3beta1/types/agent.py b/google/cloud/dialogflowcx_v3beta1/types/agent.py index c4ce4a89..579022f4 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/agent.py +++ b/google/cloud/dialogflowcx_v3beta1/types/agent.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -20,6 +22,7 @@ from google.cloud.dialogflowcx_v3beta1.types import ( advanced_settings as gcdc_advanced_settings, ) +from google.cloud.dialogflowcx_v3beta1.types import audio_config from google.cloud.dialogflowcx_v3beta1.types import flow from google.protobuf import field_mask_pb2 # type: ignore @@ -139,6 +142,10 @@ class Agent(proto.Message): agent. The settings exposed at the lower level overrides the settings exposed at the higher level. + text_to_speech_settings (google.cloud.dialogflowcx_v3beta1.types.TextToSpeechSettings): + Settings on instructing the speech + synthesizer on how to generate the output audio + content. """ name: str = proto.Field( @@ -199,6 +206,11 @@ class Agent(proto.Message): number=22, message=gcdc_advanced_settings.AdvancedSettings, ) + text_to_speech_settings: audio_config.TextToSpeechSettings = proto.Field( + proto.MESSAGE, + number=31, + message=audio_config.TextToSpeechSettings, + ) class ListAgentsRequest(proto.Message): diff --git a/google/cloud/dialogflowcx_v3beta1/types/audio_config.py b/google/cloud/dialogflowcx_v3beta1/types/audio_config.py index 91e4c183..83d630fd 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/audio_config.py +++ b/google/cloud/dialogflowcx_v3beta1/types/audio_config.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -32,6 +34,7 @@ "VoiceSelectionParams", "SynthesizeSpeechConfig", "OutputAudioConfig", + "TextToSpeechSettings", }, ) @@ -472,4 +475,25 @@ class OutputAudioConfig(proto.Message): ) +class TextToSpeechSettings(proto.Message): + r"""Settings related to speech generating. + + Attributes: + synthesize_speech_configs (MutableMapping[str, google.cloud.dialogflowcx_v3beta1.types.SynthesizeSpeechConfig]): + Configuration of how speech should be + synthesized, mapping from language + (https://dialogflow.com/docs/reference/language) + to SynthesizeSpeechConfig. + """ + + synthesize_speech_configs: MutableMapping[ + str, "SynthesizeSpeechConfig" + ] = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message="SynthesizeSpeechConfig", + ) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflowcx_v3beta1/types/changelog.py b/google/cloud/dialogflowcx_v3beta1/types/changelog.py index a5f861b2..6703a532 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/changelog.py +++ b/google/cloud/dialogflowcx_v3beta1/types/changelog.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/deployment.py b/google/cloud/dialogflowcx_v3beta1/types/deployment.py index 579e2307..734e767e 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/deployment.py +++ b/google/cloud/dialogflowcx_v3beta1/types/deployment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/entity_type.py b/google/cloud/dialogflowcx_v3beta1/types/entity_type.py index 95bd34f1..dd253539 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/entity_type.py +++ b/google/cloud/dialogflowcx_v3beta1/types/entity_type.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/environment.py b/google/cloud/dialogflowcx_v3beta1/types/environment.py index 23bc22a5..b0a8865f 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/environment.py +++ b/google/cloud/dialogflowcx_v3beta1/types/environment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore @@ -73,10 +75,10 @@ class Environment(proto.Message): characters. If exceeded, the request is rejected. version_configs (MutableSequence[google.cloud.dialogflowcx_v3beta1.types.Environment.VersionConfig]): - Required. A list of configurations for flow versions. You - should include version configs for all flows that are - reachable from [``Start Flow``][Agent.start_flow] in the - agent. Otherwise, an error will be returned. + A list of configurations for flow versions. You should + include version configs for all flows that are reachable + from [``Start Flow``][Agent.start_flow] in the agent. + Otherwise, an error will be returned. update_time (google.protobuf.timestamp_pb2.Timestamp): Output only. Update time of this environment. test_cases_config (google.cloud.dialogflowcx_v3beta1.types.Environment.TestCasesConfig): diff --git a/google/cloud/dialogflowcx_v3beta1/types/experiment.py b/google/cloud/dialogflowcx_v3beta1/types/experiment.py index 289a75e0..8de9c173 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/experiment.py +++ b/google/cloud/dialogflowcx_v3beta1/types/experiment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/flow.py b/google/cloud/dialogflowcx_v3beta1/types/flow.py index 47d16b27..16039ff4 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/flow.py +++ b/google/cloud/dialogflowcx_v3beta1/types/flow.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/fulfillment.py b/google/cloud/dialogflowcx_v3beta1/types/fulfillment.py index a893cada..3f2c88d9 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/fulfillment.py +++ b/google/cloud/dialogflowcx_v3beta1/types/fulfillment.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/gcs.py b/google/cloud/dialogflowcx_v3beta1/types/gcs.py new file mode 100644 index 00000000..54e0e48e --- /dev/null +++ b/google/cloud/dialogflowcx_v3beta1/types/gcs.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 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. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.dialogflow.cx.v3beta1", + manifest={ + "GcsDestination", + }, +) + + +class GcsDestination(proto.Message): + r"""Google Cloud Storage location for a Dialogflow operation that + writes or exports objects (e.g. exported agent or transcripts) + outside of Dialogflow. + + Attributes: + uri (str): + Required. The Google Cloud Storage URI for + the exported objects. A URI is of the form: + gs://bucket/object-name-or-prefix + Whether a full object name, or just a prefix, + its usage depends on the Dialogflow operation. + """ + + uri: str = proto.Field( + proto.STRING, + number=1, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflowcx_v3beta1/types/intent.py b/google/cloud/dialogflowcx_v3beta1/types/intent.py index 1d26309e..806eeab5 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/intent.py +++ b/google/cloud/dialogflowcx_v3beta1/types/intent.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/page.py b/google/cloud/dialogflowcx_v3beta1/types/page.py index 46959cd7..e8ea16bd 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/page.py +++ b/google/cloud/dialogflowcx_v3beta1/types/page.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/response_message.py b/google/cloud/dialogflowcx_v3beta1/types/response_message.py index 346818b4..22e497b2 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/response_message.py +++ b/google/cloud/dialogflowcx_v3beta1/types/response_message.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/security_settings.py b/google/cloud/dialogflowcx_v3beta1/types/security_settings.py index 2ed93e8f..889ac75a 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/security_settings.py +++ b/google/cloud/dialogflowcx_v3beta1/types/security_settings.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/session.py b/google/cloud/dialogflowcx_v3beta1/types/session.py index 5017d9ba..e0025e9c 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/session.py +++ b/google/cloud/dialogflowcx_v3beta1/types/session.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/session_entity_type.py b/google/cloud/dialogflowcx_v3beta1/types/session_entity_type.py index 05eab442..58d9fdde 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/session_entity_type.py +++ b/google/cloud/dialogflowcx_v3beta1/types/session_entity_type.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/test_case.py b/google/cloud/dialogflowcx_v3beta1/types/test_case.py index cf9d1d42..e24a1259 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/test_case.py +++ b/google/cloud/dialogflowcx_v3beta1/types/test_case.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/transition_route_group.py b/google/cloud/dialogflowcx_v3beta1/types/transition_route_group.py index 5c702091..6dcf22bc 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/transition_route_group.py +++ b/google/cloud/dialogflowcx_v3beta1/types/transition_route_group.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/validation_message.py b/google/cloud/dialogflowcx_v3beta1/types/validation_message.py index e57443b8..153db63e 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/validation_message.py +++ b/google/cloud/dialogflowcx_v3beta1/types/validation_message.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/version.py b/google/cloud/dialogflowcx_v3beta1/types/version.py index 6b2732b5..df529e24 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/version.py +++ b/google/cloud/dialogflowcx_v3beta1/types/version.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/google/cloud/dialogflowcx_v3beta1/types/webhook.py b/google/cloud/dialogflowcx_v3beta1/types/webhook.py index af782376..c6230740 100644 --- a/google/cloud/dialogflowcx_v3beta1/types/webhook.py +++ b/google/cloud/dialogflowcx_v3beta1/types/webhook.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations + from typing import MutableMapping, MutableSequence import proto # type: ignore diff --git a/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_async.py b/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_async.py index 74fb0652..25c075eb 100644 --- a/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_async.py +++ b/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_async.py @@ -41,7 +41,6 @@ async def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.CreateEnvironmentRequest( parent="parent_value", diff --git a/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_sync.py b/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_sync.py index 1fdd6ab1..0df85233 100644 --- a/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_sync.py +++ b/samples/generated_samples/dialogflow_v3_generated_environments_create_environment_sync.py @@ -41,7 +41,6 @@ def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.CreateEnvironmentRequest( parent="parent_value", diff --git a/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_async.py b/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_async.py index 9f321f7e..ac75ae15 100644 --- a/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_async.py +++ b/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_async.py @@ -41,7 +41,6 @@ async def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.UpdateEnvironmentRequest( environment=environment, diff --git a/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_sync.py b/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_sync.py index 5ae00fb1..e5f61a84 100644 --- a/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_sync.py +++ b/samples/generated_samples/dialogflow_v3_generated_environments_update_environment_sync.py @@ -41,7 +41,6 @@ def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3.UpdateEnvironmentRequest( environment=environment, diff --git a/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_async.py b/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_async.py index aa1e90a8..308d8803 100644 --- a/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_async.py +++ b/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_async.py @@ -41,7 +41,6 @@ async def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.CreateEnvironmentRequest( parent="parent_value", diff --git a/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_sync.py b/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_sync.py index 52a738e5..4463c470 100644 --- a/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_sync.py +++ b/samples/generated_samples/dialogflow_v3beta1_generated_environments_create_environment_sync.py @@ -41,7 +41,6 @@ def sample_create_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.CreateEnvironmentRequest( parent="parent_value", diff --git a/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_async.py b/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_async.py index dfc8e8e2..57fefda8 100644 --- a/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_async.py +++ b/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_async.py @@ -41,7 +41,6 @@ async def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.UpdateEnvironmentRequest( environment=environment, diff --git a/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_sync.py b/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_sync.py index f89dd86a..6cbc9325 100644 --- a/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_sync.py +++ b/samples/generated_samples/dialogflow_v3beta1_generated_environments_update_environment_sync.py @@ -41,7 +41,6 @@ def sample_update_environment(): # Initialize request argument(s) environment = dialogflowcx_v3beta1.Environment() environment.display_name = "display_name_value" - environment.version_configs.version = "version_value" request = dialogflowcx_v3beta1.UpdateEnvironmentRequest( environment=environment, diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json index a81f2e6e..4fe8e9ba 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json @@ -2958,12 +2958,12 @@ "regionTag": "dialogflow_v3_generated_Environments_CreateEnvironment_async", "segments": [ { - "end": 60, + "end": 59, "start": 27, "type": "FULL" }, { - "end": 60, + "end": 59, "start": 27, "type": "SHORT" }, @@ -2973,18 +2973,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 50, + "end": 49, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 57, - "start": 51, + "end": 56, + "start": 50, "type": "REQUEST_EXECUTION" }, { - "end": 61, - "start": 58, + "end": 60, + "start": 57, "type": "RESPONSE_HANDLING" } ], @@ -3042,12 +3042,12 @@ "regionTag": "dialogflow_v3_generated_Environments_CreateEnvironment_sync", "segments": [ { - "end": 60, + "end": 59, "start": 27, "type": "FULL" }, { - "end": 60, + "end": 59, "start": 27, "type": "SHORT" }, @@ -3057,18 +3057,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 50, + "end": 49, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 57, - "start": 51, + "end": 56, + "start": 50, "type": "REQUEST_EXECUTION" }, { - "end": 61, - "start": 58, + "end": 60, + "start": 57, "type": "RESPONSE_HANDLING" } ], @@ -4232,12 +4232,12 @@ "regionTag": "dialogflow_v3_generated_Environments_UpdateEnvironment_async", "segments": [ { - "end": 59, + "end": 58, "start": 27, "type": "FULL" }, { - "end": 59, + "end": 58, "start": 27, "type": "SHORT" }, @@ -4247,18 +4247,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 49, + "end": 48, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 56, - "start": 50, + "end": 55, + "start": 49, "type": "REQUEST_EXECUTION" }, { - "end": 60, - "start": 57, + "end": 59, + "start": 56, "type": "RESPONSE_HANDLING" } ], @@ -4316,12 +4316,12 @@ "regionTag": "dialogflow_v3_generated_Environments_UpdateEnvironment_sync", "segments": [ { - "end": 59, + "end": 58, "start": 27, "type": "FULL" }, { - "end": 59, + "end": 58, "start": 27, "type": "SHORT" }, @@ -4331,18 +4331,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 49, + "end": 48, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 56, - "start": 50, + "end": 55, + "start": 49, "type": "REQUEST_EXECUTION" }, { - "end": 60, - "start": 57, + "end": 59, + "start": 56, "type": "RESPONSE_HANDLING" } ], diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json index 1df013e0..bddeff21 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json @@ -2958,12 +2958,12 @@ "regionTag": "dialogflow_v3beta1_generated_Environments_CreateEnvironment_async", "segments": [ { - "end": 60, + "end": 59, "start": 27, "type": "FULL" }, { - "end": 60, + "end": 59, "start": 27, "type": "SHORT" }, @@ -2973,18 +2973,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 50, + "end": 49, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 57, - "start": 51, + "end": 56, + "start": 50, "type": "REQUEST_EXECUTION" }, { - "end": 61, - "start": 58, + "end": 60, + "start": 57, "type": "RESPONSE_HANDLING" } ], @@ -3042,12 +3042,12 @@ "regionTag": "dialogflow_v3beta1_generated_Environments_CreateEnvironment_sync", "segments": [ { - "end": 60, + "end": 59, "start": 27, "type": "FULL" }, { - "end": 60, + "end": 59, "start": 27, "type": "SHORT" }, @@ -3057,18 +3057,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 50, + "end": 49, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 57, - "start": 51, + "end": 56, + "start": 50, "type": "REQUEST_EXECUTION" }, { - "end": 61, - "start": 58, + "end": 60, + "start": 57, "type": "RESPONSE_HANDLING" } ], @@ -4232,12 +4232,12 @@ "regionTag": "dialogflow_v3beta1_generated_Environments_UpdateEnvironment_async", "segments": [ { - "end": 59, + "end": 58, "start": 27, "type": "FULL" }, { - "end": 59, + "end": 58, "start": 27, "type": "SHORT" }, @@ -4247,18 +4247,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 49, + "end": 48, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 56, - "start": 50, + "end": 55, + "start": 49, "type": "REQUEST_EXECUTION" }, { - "end": 60, - "start": 57, + "end": 59, + "start": 56, "type": "RESPONSE_HANDLING" } ], @@ -4316,12 +4316,12 @@ "regionTag": "dialogflow_v3beta1_generated_Environments_UpdateEnvironment_sync", "segments": [ { - "end": 59, + "end": 58, "start": 27, "type": "FULL" }, { - "end": 59, + "end": 58, "start": 27, "type": "SHORT" }, @@ -4331,18 +4331,18 @@ "type": "CLIENT_INITIALIZATION" }, { - "end": 49, + "end": 48, "start": 41, "type": "REQUEST_INITIALIZATION" }, { - "end": 56, - "start": 50, + "end": 55, + "start": 49, "type": "REQUEST_EXECUTION" }, { - "end": 60, - "start": 57, + "end": 59, + "start": 56, "type": "RESPONSE_HANDLING" } ], diff --git a/scripts/fixup_dialogflowcx_v3_keywords.py b/scripts/fixup_dialogflowcx_v3_keywords.py index 24a0ece5..a86e407c 100644 --- a/scripts/fixup_dialogflowcx_v3_keywords.py +++ b/scripts/fixup_dialogflowcx_v3_keywords.py @@ -113,7 +113,7 @@ class dialogflowcxCallTransformer(cst.CSTTransformer): 'list_webhooks': ('parent', 'page_size', 'page_token', ), 'load_version': ('name', 'allow_override_agent_resources', ), 'lookup_environment_history': ('name', 'page_size', 'page_token', ), - 'match_intent': ('session', 'query_input', 'query_params', ), + 'match_intent': ('session', 'query_input', 'query_params', 'persist_parameter_changes', ), 'restore_agent': ('name', 'agent_uri', 'agent_content', 'restore_option', ), 'run_continuous_test': ('environment', ), 'run_test_case': ('name', 'environment', ), diff --git a/tests/unit/gapic/dialogflowcx_v3/test_agents.py b/tests/unit/gapic/dialogflowcx_v3/test_agents.py index f08ef1ea..acd949ca 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_agents.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_agents.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -48,7 +55,9 @@ from google.cloud.dialogflowcx_v3.types import advanced_settings from google.cloud.dialogflowcx_v3.types import agent from google.cloud.dialogflowcx_v3.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3.types import audio_config from google.cloud.dialogflowcx_v3.types import flow +from google.cloud.dialogflowcx_v3.types import gcs from google.cloud.location import locations_pb2 from google.longrunning import operations_pb2 from google.oauth2 import service_account @@ -101,6 +110,7 @@ def test__get_default_mtls_endpoint(): [ (AgentsClient, "grpc"), (AgentsAsyncClient, "grpc_asyncio"), + (AgentsClient, "rest"), ], ) def test_agents_client_from_service_account_info(client_class, transport_name): @@ -114,7 +124,11 @@ def test_agents_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -122,6 +136,7 @@ def test_agents_client_from_service_account_info(client_class, transport_name): [ (transports.AgentsGrpcTransport, "grpc"), (transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.AgentsRestTransport, "rest"), ], ) def test_agents_client_service_account_always_use_jwt(transport_class, transport_name): @@ -145,6 +160,7 @@ def test_agents_client_service_account_always_use_jwt(transport_class, transport [ (AgentsClient, "grpc"), (AgentsAsyncClient, "grpc_asyncio"), + (AgentsClient, "rest"), ], ) def test_agents_client_from_service_account_file(client_class, transport_name): @@ -165,13 +181,18 @@ def test_agents_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_agents_client_get_transport_class(): transport = AgentsClient.get_transport_class() available_transports = [ transports.AgentsGrpcTransport, + transports.AgentsRestTransport, ] assert transport in available_transports @@ -184,6 +205,7 @@ def test_agents_client_get_transport_class(): [ (AgentsClient, transports.AgentsGrpcTransport, "grpc"), (AgentsAsyncClient, transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (AgentsClient, transports.AgentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -323,6 +345,8 @@ def test_agents_client_client_options(client_class, transport_class, transport_n "grpc_asyncio", "false", ), + (AgentsClient, transports.AgentsRestTransport, "rest", "true"), + (AgentsClient, transports.AgentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -508,6 +532,7 @@ def test_agents_client_get_mtls_endpoint_and_cert_source(client_class): [ (AgentsClient, transports.AgentsGrpcTransport, "grpc"), (AgentsAsyncClient, transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (AgentsClient, transports.AgentsRestTransport, "rest"), ], ) def test_agents_client_client_options_scopes( @@ -543,6 +568,7 @@ def test_agents_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (AgentsClient, transports.AgentsRestTransport, "rest", None), ], ) def test_agents_client_client_options_credentials_file( @@ -2811,258 +2837,2690 @@ async def test_get_agent_validation_result_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.AgentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + agent.ListAgentsRequest, + dict, + ], +) +def test_list_agents_rest(request_type): + client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = AgentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = AgentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = AgentsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = AgentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = AgentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_agents(request) + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListAgentsPager) + assert response.next_page_token == "next_page_token_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - client = AgentsClient(transport=transport) - assert client.transport is transport +def test_list_agents_rest_required_fields(request_type=agent.ListAgentsRequest): + transport_class = transports.AgentsRestTransport -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - channel = transport.grpc_channel - assert channel - transport = transports.AgentsGrpcAsyncIOTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - channel = transport.grpc_channel - assert channel + # verify fields with default values are dropped + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_agents._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + # verify required fields with default values are now present + jsonified_request["parent"] = "parent_value" -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = AgentsClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_agents._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) ) - assert transport.kind == transport_name + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_agents(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_agents_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.AgentsGrpcTransport, + + unset_fields = transport.list_agents._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) ) -def test_agents_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.AgentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_agents_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_list_agents" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_list_agents" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ListAgentsRequest.pb(agent.ListAgentsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.ListAgentsResponse.to_json( + agent.ListAgentsResponse() ) + request = agent.ListAgentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.ListAgentsResponse() -def test_agents_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.AgentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_agents( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_agents", - "get_agent", - "create_agent", - "update_agent", - "delete_agent", - "export_agent", - "restore_agent", - "validate_agent", - "get_agent_validation_result", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", - ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) + pre.assert_called_once() + post.assert_called_once() - with pytest.raises(NotImplementedError): - transport.close() - # Additionally, the LRO client (a property) should - # also raise NotImplementedError - with pytest.raises(NotImplementedError): - transport.operations_client +def test_list_agents_rest_bad_request( + transport: str = "rest", request_type=agent.ListAgentsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_agents(request) -def test_agents_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.AgentsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", - ) +def test_list_agents_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) -def test_agents_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.AgentsTransport() - adc.assert_called_once() + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse() + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} -def test_agents_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - AgentsClient() - adc.assert_called_once_with( - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id=None, + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], -) -def test_agents_transport_auth_adc(transport_class): - # If credentials and host are not provided, the transport class should use - # ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class(quota_project_id="octopus", scopes=["1", "2"]) - adc.assert_called_once_with( - scopes=["1", "2"], - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + client.list_agents(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*}/agents" % client.transport._host, + args[1], ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], +def test_list_agents_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_agents( + agent.ListAgentsRequest(), + parent="parent_value", + ) + + +def test_list_agents_rest_pager(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + agent.Agent(), + agent.Agent(), + ], + next_page_token="abc", + ), + agent.ListAgentsResponse( + agents=[], + next_page_token="def", + ), + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + ], + next_page_token="ghi", + ), + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + agent.Agent(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(agent.ListAgentsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2"} + + pager = client.list_agents(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, agent.Agent) for i in results) + + pages = list(client.list_agents(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + agent.GetAgentRequest, + dict, + ], +) +def test_get_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_get_agent_rest_required_fields(request_type=agent.GetAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_get_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_get_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.GetAgentRequest.pb(agent.GetAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.Agent.to_json(agent.Agent()) + + request = agent.GetAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.Agent() + + client.get_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_agent_rest_bad_request( + transport: str = "rest", request_type=agent.GetAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_agent(request) + + +def test_get_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*}" % client.transport._host, + args[1], + ) + + +def test_get_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_agent( + agent.GetAgentRequest(), + name="name_value", + ) + + +def test_get_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_agent.CreateAgentRequest, + dict, + ], +) +def test_create_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["agent"] = { + "name": "name_value", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_create_agent_rest_required_fields(request_type=gcdc_agent.CreateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_agent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "agent", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_create_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_create_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_agent.CreateAgentRequest.pb(gcdc_agent.CreateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_agent.Agent.to_json(gcdc_agent.Agent()) + + request = gcdc_agent.CreateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_agent.Agent() + + client.create_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_agent_rest_bad_request( + transport: str = "rest", request_type=gcdc_agent.CreateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["agent"] = { + "name": "name_value", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_agent(request) + + +def test_create_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + agent=gcdc_agent.Agent(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*}/agents" % client.transport._host, + args[1], + ) + + +def test_create_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_agent( + gcdc_agent.CreateAgentRequest(), + parent="parent_value", + agent=gcdc_agent.Agent(name="name_value"), + ) + + +def test_create_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_agent.UpdateAgentRequest, + dict, + ], +) +def test_update_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + request_init["agent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_update_agent_rest_required_fields(request_type=gcdc_agent.UpdateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_agent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("agent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_update_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_update_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_agent.UpdateAgentRequest.pb(gcdc_agent.UpdateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_agent.Agent.to_json(gcdc_agent.Agent()) + + request = gcdc_agent.UpdateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_agent.Agent() + + client.update_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_agent_rest_bad_request( + transport: str = "rest", request_type=gcdc_agent.UpdateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + request_init["agent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_agent(request) + + +def test_update_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + + # get truthy value for each flattened field + mock_args = dict( + agent=gcdc_agent.Agent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{agent.name=projects/*/locations/*/agents/*}" + % client.transport._host, + args[1], + ) + + +def test_update_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_agent( + gcdc_agent.UpdateAgentRequest(), + agent=gcdc_agent.Agent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.DeleteAgentRequest, + dict, + ], +) +def test_delete_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_agent(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_agent_rest_required_fields(request_type=agent.DeleteAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "pre_delete_agent" + ) as pre: + pre.assert_not_called() + pb_message = agent.DeleteAgentRequest.pb(agent.DeleteAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = agent.DeleteAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_agent_rest_bad_request( + transport: str = "rest", request_type=agent.DeleteAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_agent(request) + + +def test_delete_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*}" % client.transport._host, + args[1], + ) + + +def test_delete_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_agent( + agent.DeleteAgentRequest(), + name="name_value", + ) + + +def test_delete_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.ExportAgentRequest, + dict, + ], +) +def test_export_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_agent(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_agent_rest_required_fields(request_type=agent.ExportAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.AgentsRestInterceptor, "post_export_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_export_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ExportAgentRequest.pb(agent.ExportAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = agent.ExportAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_agent_rest_bad_request( + transport: str = "rest", request_type=agent.ExportAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_agent(request) + + +def test_export_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.RestoreAgentRequest, + dict, + ], +) +def test_restore_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.restore_agent(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_restore_agent_rest_required_fields(request_type=agent.RestoreAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).restore_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).restore_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.restore_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_restore_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.restore_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_restore_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.AgentsRestInterceptor, "post_restore_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_restore_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.RestoreAgentRequest.pb(agent.RestoreAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = agent.RestoreAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.restore_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_restore_agent_rest_bad_request( + transport: str = "rest", request_type=agent.RestoreAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.restore_agent(request) + + +def test_restore_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.ValidateAgentRequest, + dict, + ], +) +def test_validate_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.AgentValidationResult) + assert response.name == "name_value" + + +def test_validate_agent_rest_required_fields(request_type=agent.ValidateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_validate_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_validate_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ValidateAgentRequest.pb(agent.ValidateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.AgentValidationResult.to_json( + agent.AgentValidationResult() + ) + + request = agent.ValidateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.AgentValidationResult() + + client.validate_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_agent_rest_bad_request( + transport: str = "rest", request_type=agent.ValidateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_agent(request) + + +def test_validate_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.GetAgentValidationResultRequest, + dict, + ], +) +def test_get_agent_validation_result_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_agent_validation_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.AgentValidationResult) + assert response.name == "name_value" + + +def test_get_agent_validation_result_rest_required_fields( + request_type=agent.GetAgentValidationResultRequest, +): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent_validation_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent_validation_result._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_agent_validation_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_agent_validation_result_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_agent_validation_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_agent_validation_result_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_get_agent_validation_result" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_get_agent_validation_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.GetAgentValidationResultRequest.pb( + agent.GetAgentValidationResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.AgentValidationResult.to_json( + agent.AgentValidationResult() + ) + + request = agent.GetAgentValidationResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.AgentValidationResult() + + client.get_agent_validation_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_agent_validation_result_rest_bad_request( + transport: str = "rest", request_type=agent.GetAgentValidationResultRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_agent_validation_result(request) + + +def test_get_agent_validation_result_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_agent_validation_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/validationResult}" + % client.transport._host, + args[1], + ) + + +def test_get_agent_validation_result_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_agent_validation_result( + agent.GetAgentValidationResultRequest(), + name="name_value", + ) + + +def test_get_agent_validation_result_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = AgentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = AgentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = AgentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.AgentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + transports.AgentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = AgentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.AgentsGrpcTransport, + ) + + +def test_agents_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.AgentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_agents_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.AgentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_agents", + "get_agent", + "create_agent", + "update_agent", + "delete_agent", + "export_agent", + "restore_agent", + "validate_agent", + "get_agent_validation_result", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Additionally, the LRO client (a property) should + # also raise NotImplementedError + with pytest.raises(NotImplementedError): + transport.operations_client + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_agents_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.AgentsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_agents_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.agents.transports.AgentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.AgentsTransport() + adc.assert_called_once() + + +def test_agents_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + AgentsClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + ], +) +def test_agents_transport_auth_adc(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + adc.assert_called_once_with( + scopes=["1", "2"], + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + transports.AgentsRestTransport, + ], ) def test_agents_transport_auth_gdch_credentials(transport_class): host = "https://language.com" @@ -3159,11 +5617,40 @@ def test_agents_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_agents_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.AgentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_agents_rest_lro_client(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_agents_host_no_port(transport_name): @@ -3174,7 +5661,11 @@ def test_agents_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3182,6 +5673,7 @@ def test_agents_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_agents_host_with_port(transport_name): @@ -3192,7 +5684,57 @@ def test_agents_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_agents_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = AgentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = AgentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_agents._session + session2 = client2.transport.list_agents._session + assert session1 != session2 + session1 = client1.transport.get_agent._session + session2 = client2.transport.get_agent._session + assert session1 != session2 + session1 = client1.transport.create_agent._session + session2 = client2.transport.create_agent._session + assert session1 != session2 + session1 = client1.transport.update_agent._session + session2 = client2.transport.update_agent._session + assert session1 != session2 + session1 = client1.transport.delete_agent._session + session2 = client2.transport.delete_agent._session + assert session1 != session2 + session1 = client1.transport.export_agent._session + session2 = client2.transport.export_agent._session + assert session1 != session2 + session1 = client1.transport.restore_agent._session + session2 = client2.transport.restore_agent._session + assert session1 != session2 + session1 = client1.transport.validate_agent._session + session2 = client2.transport.validate_agent._session + assert session1 != session2 + session1 = client1.transport.get_agent_validation_result._session + session2 = client2.transport.get_agent_validation_result._session + assert session1 != session2 def test_agents_grpc_transport_channel(): @@ -3654,6 +6196,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4371,6 +7199,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4388,6 +7217,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_changelogs.py b/tests/unit/gapic/dialogflowcx_v3/test_changelogs.py index a056b25e..dbfe6e1d 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_changelogs.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_changelogs.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -95,6 +102,7 @@ def test__get_default_mtls_endpoint(): [ (ChangelogsClient, "grpc"), (ChangelogsAsyncClient, "grpc_asyncio"), + (ChangelogsClient, "rest"), ], ) def test_changelogs_client_from_service_account_info(client_class, transport_name): @@ -108,7 +116,11 @@ def test_changelogs_client_from_service_account_info(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -116,6 +128,7 @@ def test_changelogs_client_from_service_account_info(client_class, transport_nam [ (transports.ChangelogsGrpcTransport, "grpc"), (transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ChangelogsRestTransport, "rest"), ], ) def test_changelogs_client_service_account_always_use_jwt( @@ -141,6 +154,7 @@ def test_changelogs_client_service_account_always_use_jwt( [ (ChangelogsClient, "grpc"), (ChangelogsAsyncClient, "grpc_asyncio"), + (ChangelogsClient, "rest"), ], ) def test_changelogs_client_from_service_account_file(client_class, transport_name): @@ -161,13 +175,18 @@ def test_changelogs_client_from_service_account_file(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_changelogs_client_get_transport_class(): transport = ChangelogsClient.get_transport_class() available_transports = [ transports.ChangelogsGrpcTransport, + transports.ChangelogsRestTransport, ] assert transport in available_transports @@ -184,6 +203,7 @@ def test_changelogs_client_get_transport_class(): transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest"), ], ) @mock.patch.object( @@ -327,6 +347,8 @@ def test_changelogs_client_client_options( "grpc_asyncio", "false", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", "true"), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -520,6 +542,7 @@ def test_changelogs_client_get_mtls_endpoint_and_cert_source(client_class): transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest"), ], ) def test_changelogs_client_client_options_scopes( @@ -555,6 +578,7 @@ def test_changelogs_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", None), ], ) def test_changelogs_client_client_options_credentials_file( @@ -1331,6 +1355,624 @@ async def test_get_changelog_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + changelog.ListChangelogsRequest, + dict, + ], +) +def test_list_changelogs_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_changelogs(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListChangelogsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_changelogs_rest_required_fields( + request_type=changelog.ListChangelogsRequest, +): + transport_class = transports.ChangelogsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_changelogs._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_changelogs._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "filter", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_changelogs(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_changelogs_rest_unset_required_fields(): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_changelogs._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "filter", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_changelogs_rest_interceptors(null_interceptor): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ChangelogsRestInterceptor(), + ) + client = ChangelogsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ChangelogsRestInterceptor, "post_list_changelogs" + ) as post, mock.patch.object( + transports.ChangelogsRestInterceptor, "pre_list_changelogs" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = changelog.ListChangelogsRequest.pb( + changelog.ListChangelogsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = changelog.ListChangelogsResponse.to_json( + changelog.ListChangelogsResponse() + ) + + request = changelog.ListChangelogsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = changelog.ListChangelogsResponse() + + client.list_changelogs( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_changelogs_rest_bad_request( + transport: str = "rest", request_type=changelog.ListChangelogsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_changelogs(request) + + +def test_list_changelogs_rest_flattened(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_changelogs(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/changelogs" + % client.transport._host, + args[1], + ) + + +def test_list_changelogs_rest_flattened_error(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_changelogs( + changelog.ListChangelogsRequest(), + parent="parent_value", + ) + + +def test_list_changelogs_rest_pager(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + changelog.Changelog(), + changelog.Changelog(), + ], + next_page_token="abc", + ), + changelog.ListChangelogsResponse( + changelogs=[], + next_page_token="def", + ), + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + ], + next_page_token="ghi", + ), + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + changelog.Changelog(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(changelog.ListChangelogsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_changelogs(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, changelog.Changelog) for i in results) + + pages = list(client.list_changelogs(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + changelog.GetChangelogRequest, + dict, + ], +) +def test_get_changelog_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog( + name="name_value", + user_email="user_email_value", + display_name="display_name_value", + action="action_value", + type_="type__value", + resource="resource_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_changelog(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, changelog.Changelog) + assert response.name == "name_value" + assert response.user_email == "user_email_value" + assert response.display_name == "display_name_value" + assert response.action == "action_value" + assert response.type_ == "type__value" + assert response.resource == "resource_value" + + +def test_get_changelog_rest_required_fields(request_type=changelog.GetChangelogRequest): + transport_class = transports.ChangelogsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_changelog._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_changelog._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_changelog(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_changelog_rest_unset_required_fields(): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_changelog._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_changelog_rest_interceptors(null_interceptor): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ChangelogsRestInterceptor(), + ) + client = ChangelogsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ChangelogsRestInterceptor, "post_get_changelog" + ) as post, mock.patch.object( + transports.ChangelogsRestInterceptor, "pre_get_changelog" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = changelog.GetChangelogRequest.pb(changelog.GetChangelogRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = changelog.Changelog.to_json(changelog.Changelog()) + + request = changelog.GetChangelogRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = changelog.Changelog() + + client.get_changelog( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_changelog_rest_bad_request( + transport: str = "rest", request_type=changelog.GetChangelogRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_changelog(request) + + +def test_get_changelog_rest_flattened(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_changelog(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/changelogs/*}" + % client.transport._host, + args[1], + ) + + +def test_get_changelog_rest_flattened_error(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_changelog( + changelog.GetChangelogRequest(), + name="name_value", + ) + + +def test_get_changelog_rest_error(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.ChangelogsGrpcTransport( @@ -1412,6 +2054,7 @@ def test_transport_get_channel(): [ transports.ChangelogsGrpcTransport, transports.ChangelogsGrpcAsyncIOTransport, + transports.ChangelogsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1426,6 +2069,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1570,6 +2214,7 @@ def test_changelogs_transport_auth_adc(transport_class): [ transports.ChangelogsGrpcTransport, transports.ChangelogsGrpcAsyncIOTransport, + transports.ChangelogsRestTransport, ], ) def test_changelogs_transport_auth_gdch_credentials(transport_class): @@ -1667,11 +2312,23 @@ def test_changelogs_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_changelogs_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ChangelogsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_changelogs_host_no_port(transport_name): @@ -1682,7 +2339,11 @@ def test_changelogs_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1690,6 +2351,7 @@ def test_changelogs_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_changelogs_host_with_port(transport_name): @@ -1700,7 +2362,36 @@ def test_changelogs_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_changelogs_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ChangelogsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ChangelogsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_changelogs._session + session2 = client2.transport.list_changelogs._session + assert session1 != session2 + session1 = client1.transport.get_changelog._session + session2 = client2.transport.get_changelog._session + assert session1 != session2 def test_changelogs_grpc_transport_channel(): @@ -1990,6 +2681,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = ChangelogsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2707,6 +3684,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2724,6 +3702,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_deployments.py b/tests/unit/gapic/dialogflowcx_v3/test_deployments.py index e37b778a..e666a063 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_deployments.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_deployments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -95,6 +102,7 @@ def test__get_default_mtls_endpoint(): [ (DeploymentsClient, "grpc"), (DeploymentsAsyncClient, "grpc_asyncio"), + (DeploymentsClient, "rest"), ], ) def test_deployments_client_from_service_account_info(client_class, transport_name): @@ -108,7 +116,11 @@ def test_deployments_client_from_service_account_info(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -116,6 +128,7 @@ def test_deployments_client_from_service_account_info(client_class, transport_na [ (transports.DeploymentsGrpcTransport, "grpc"), (transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.DeploymentsRestTransport, "rest"), ], ) def test_deployments_client_service_account_always_use_jwt( @@ -141,6 +154,7 @@ def test_deployments_client_service_account_always_use_jwt( [ (DeploymentsClient, "grpc"), (DeploymentsAsyncClient, "grpc_asyncio"), + (DeploymentsClient, "rest"), ], ) def test_deployments_client_from_service_account_file(client_class, transport_name): @@ -161,13 +175,18 @@ def test_deployments_client_from_service_account_file(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_deployments_client_get_transport_class(): transport = DeploymentsClient.get_transport_class() available_transports = [ transports.DeploymentsGrpcTransport, + transports.DeploymentsRestTransport, ] assert transport in available_transports @@ -184,6 +203,7 @@ def test_deployments_client_get_transport_class(): transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -327,6 +347,8 @@ def test_deployments_client_client_options( "grpc_asyncio", "false", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", "true"), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -520,6 +542,7 @@ def test_deployments_client_get_mtls_endpoint_and_cert_source(client_class): transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest"), ], ) def test_deployments_client_client_options_scopes( @@ -555,6 +578,7 @@ def test_deployments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", None), ], ) def test_deployments_client_client_options_credentials_file( @@ -1323,6 +1347,632 @@ async def test_get_deployment_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + deployment.ListDeploymentsRequest, + dict, + ], +) +def test_list_deployments_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_deployments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListDeploymentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_deployments_rest_required_fields( + request_type=deployment.ListDeploymentsRequest, +): + transport_class = transports.DeploymentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_deployments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_deployments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_deployments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_deployments_rest_unset_required_fields(): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_deployments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_deployments_rest_interceptors(null_interceptor): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.DeploymentsRestInterceptor(), + ) + client = DeploymentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.DeploymentsRestInterceptor, "post_list_deployments" + ) as post, mock.patch.object( + transports.DeploymentsRestInterceptor, "pre_list_deployments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = deployment.ListDeploymentsRequest.pb( + deployment.ListDeploymentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = deployment.ListDeploymentsResponse.to_json( + deployment.ListDeploymentsResponse() + ) + + request = deployment.ListDeploymentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = deployment.ListDeploymentsResponse() + + client.list_deployments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_deployments_rest_bad_request( + transport: str = "rest", request_type=deployment.ListDeploymentsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_deployments(request) + + +def test_list_deployments_rest_flattened(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_deployments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/environments/*}/deployments" + % client.transport._host, + args[1], + ) + + +def test_list_deployments_rest_flattened_error(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_deployments( + deployment.ListDeploymentsRequest(), + parent="parent_value", + ) + + +def test_list_deployments_rest_pager(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + deployment.Deployment(), + deployment.Deployment(), + ], + next_page_token="abc", + ), + deployment.ListDeploymentsResponse( + deployments=[], + next_page_token="def", + ), + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + ], + next_page_token="ghi", + ), + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + deployment.Deployment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + deployment.ListDeploymentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.list_deployments(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, deployment.Deployment) for i in results) + + pages = list(client.list_deployments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + deployment.GetDeploymentRequest, + dict, + ], +) +def test_get_deployment_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment( + name="name_value", + flow_version="flow_version_value", + state=deployment.Deployment.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_deployment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, deployment.Deployment) + assert response.name == "name_value" + assert response.flow_version == "flow_version_value" + assert response.state == deployment.Deployment.State.RUNNING + + +def test_get_deployment_rest_required_fields( + request_type=deployment.GetDeploymentRequest, +): + transport_class = transports.DeploymentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_deployment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_deployment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_deployment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_deployment_rest_unset_required_fields(): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_deployment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_deployment_rest_interceptors(null_interceptor): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.DeploymentsRestInterceptor(), + ) + client = DeploymentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.DeploymentsRestInterceptor, "post_get_deployment" + ) as post, mock.patch.object( + transports.DeploymentsRestInterceptor, "pre_get_deployment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = deployment.GetDeploymentRequest.pb( + deployment.GetDeploymentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = deployment.Deployment.to_json( + deployment.Deployment() + ) + + request = deployment.GetDeploymentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = deployment.Deployment() + + client.get_deployment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_deployment_rest_bad_request( + transport: str = "rest", request_type=deployment.GetDeploymentRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_deployment(request) + + +def test_get_deployment_rest_flattened(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_deployment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*/deployments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_deployment_rest_flattened_error(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_deployment( + deployment.GetDeploymentRequest(), + name="name_value", + ) + + +def test_get_deployment_rest_error(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.DeploymentsGrpcTransport( @@ -1404,6 +2054,7 @@ def test_transport_get_channel(): [ transports.DeploymentsGrpcTransport, transports.DeploymentsGrpcAsyncIOTransport, + transports.DeploymentsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1418,6 +2069,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1562,6 +2214,7 @@ def test_deployments_transport_auth_adc(transport_class): [ transports.DeploymentsGrpcTransport, transports.DeploymentsGrpcAsyncIOTransport, + transports.DeploymentsRestTransport, ], ) def test_deployments_transport_auth_gdch_credentials(transport_class): @@ -1659,11 +2312,23 @@ def test_deployments_grpc_transport_client_cert_source_for_mtls(transport_class) ) +def test_deployments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.DeploymentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_deployments_host_no_port(transport_name): @@ -1674,7 +2339,11 @@ def test_deployments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1682,6 +2351,7 @@ def test_deployments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_deployments_host_with_port(transport_name): @@ -1692,7 +2362,36 @@ def test_deployments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_deployments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = DeploymentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = DeploymentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_deployments._session + session2 = client2.transport.list_deployments._session + assert session1 != session2 + session1 = client1.transport.get_deployment._session + session2 = client2.transport.get_deployment._session + assert session1 != session2 def test_deployments_grpc_transport_channel(): @@ -2087,6 +2786,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = DeploymentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2804,6 +3789,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2821,6 +3807,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_entity_types.py b/tests/unit/gapic/dialogflowcx_v3/test_entity_types.py index 2ccb579c..292c5229 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_entity_types.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_entity_types.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -96,6 +103,7 @@ def test__get_default_mtls_endpoint(): [ (EntityTypesClient, "grpc"), (EntityTypesAsyncClient, "grpc_asyncio"), + (EntityTypesClient, "rest"), ], ) def test_entity_types_client_from_service_account_info(client_class, transport_name): @@ -109,7 +117,11 @@ def test_entity_types_client_from_service_account_info(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -117,6 +129,7 @@ def test_entity_types_client_from_service_account_info(client_class, transport_n [ (transports.EntityTypesGrpcTransport, "grpc"), (transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.EntityTypesRestTransport, "rest"), ], ) def test_entity_types_client_service_account_always_use_jwt( @@ -142,6 +155,7 @@ def test_entity_types_client_service_account_always_use_jwt( [ (EntityTypesClient, "grpc"), (EntityTypesAsyncClient, "grpc_asyncio"), + (EntityTypesClient, "rest"), ], ) def test_entity_types_client_from_service_account_file(client_class, transport_name): @@ -162,13 +176,18 @@ def test_entity_types_client_from_service_account_file(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_entity_types_client_get_transport_class(): transport = EntityTypesClient.get_transport_class() available_transports = [ transports.EntityTypesGrpcTransport, + transports.EntityTypesRestTransport, ] assert transport in available_transports @@ -185,6 +204,7 @@ def test_entity_types_client_get_transport_class(): transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest"), ], ) @mock.patch.object( @@ -328,6 +348,8 @@ def test_entity_types_client_client_options( "grpc_asyncio", "false", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", "true"), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -521,6 +543,7 @@ def test_entity_types_client_get_mtls_endpoint_and_cert_source(client_class): transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest"), ], ) def test_entity_types_client_client_options_scopes( @@ -556,6 +579,7 @@ def test_entity_types_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", None), ], ) def test_entity_types_client_client_options_credentials_file( @@ -2164,211 +2188,1753 @@ async def test_delete_entity_type_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.EntityTypesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + entity_type.ListEntityTypesRequest, + dict, + ], +) +def test_list_entity_types_rest(request_type): + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = EntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_entity_types(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListEntityTypesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_entity_types_rest_required_fields( + request_type=entity_type.ListEntityTypesRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_entity_types._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_entity_types._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.EntityTypesGrpcTransport( + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = EntityTypesClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_entity_types(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_entity_types_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.EntityTypesGrpcAsyncIOTransport( + unset_fields = transport.list_entity_types._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_entity_types_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_list_entity_types" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_list_entity_types" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = entity_type.ListEntityTypesRequest.pb( + entity_type.ListEntityTypesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = entity_type.ListEntityTypesResponse.to_json( + entity_type.ListEntityTypesResponse() + ) + request = entity_type.ListEntityTypesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = entity_type.ListEntityTypesResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.EntityTypesGrpcTransport, - transports.EntityTypesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_entity_types( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = EntityTypesClient.get_transport_class(transport_name)( + +def test_list_entity_types_rest_bad_request( + transport: str = "rest", request_type=entity_type.ListEntityTypesRequest +): + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_entity_types(request) + + +def test_list_entity_types_rest_flattened(): client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.EntityTypesGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse() -def test_entity_types_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.EntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -def test_entity_types_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.EntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_entity_types(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/entityTypes" + % client.transport._host, + args[1], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_entity_types", - "get_entity_type", - "create_entity_type", - "update_entity_type", - "delete_entity_type", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + +def test_list_entity_types_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_entity_types( + entity_type.ListEntityTypesRequest(), + parent="parent_value", + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_entity_types_rest_pager(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_entity_types_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.EntityTypesTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + entity_type.EntityType(), + entity_type.EntityType(), + ], + next_page_token="abc", + ), + entity_type.ListEntityTypesResponse( + entity_types=[], + next_page_token="def", + ), + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + ], + next_page_token="ghi", + ), + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + entity_type.EntityType(), + ], ), - quota_project_id="octopus", ) + # Two responses for two calls + response = response + response + # Wrap the values into proper Response objs + response = tuple( + entity_type.ListEntityTypesResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -def test_entity_types_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.EntityTypesTransport() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + pager = client.list_entity_types(request=sample_request) -def test_entity_types_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - EntityTypesClient() - adc.assert_called_once_with( + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, entity_type.EntityType) for i in results) + + pages = list(client.list_entity_types(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + entity_type.GetEntityTypeRequest, + dict, + ], +) +def test_get_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_get_entity_type_rest_required_fields( + request_type=entity_type.GetEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_get_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_get_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = entity_type.GetEntityTypeRequest.pb( + entity_type.GetEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = entity_type.EntityType.to_json( + entity_type.EntityType() + ) + + request = entity_type.GetEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = entity_type.EntityType() + + client.get_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_entity_type_rest_bad_request( + transport: str = "rest", request_type=entity_type.GetEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_entity_type(request) + + +def test_get_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_get_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_entity_type( + entity_type.GetEntityTypeRequest(), + name="name_value", + ) + + +def test_get_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_entity_type.CreateEntityTypeRequest, + dict, + ], +) +def test_create_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["entity_type"] = { + "name": "name_value", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=gcdc_entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == gcdc_entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_create_entity_type_rest_required_fields( + request_type=gcdc_entity_type.CreateEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "entityType", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_create_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_create_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_entity_type.CreateEntityTypeRequest.pb( + gcdc_entity_type.CreateEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_entity_type.EntityType.to_json( + gcdc_entity_type.EntityType() + ) + + request = gcdc_entity_type.CreateEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_entity_type.EntityType() + + client.create_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_entity_type_rest_bad_request( + transport: str = "rest", request_type=gcdc_entity_type.CreateEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["entity_type"] = { + "name": "name_value", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_entity_type(request) + + +def test_create_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + entity_type=gcdc_entity_type.EntityType(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/entityTypes" + % client.transport._host, + args[1], + ) + + +def test_create_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_entity_type( + gcdc_entity_type.CreateEntityTypeRequest(), + parent="parent_value", + entity_type=gcdc_entity_type.EntityType(name="name_value"), + ) + + +def test_create_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_entity_type.UpdateEntityTypeRequest, + dict, + ], +) +def test_update_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + request_init["entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=gcdc_entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == gcdc_entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_update_entity_type_rest_required_fields( + request_type=gcdc_entity_type.UpdateEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("entityType",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_update_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_update_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_entity_type.UpdateEntityTypeRequest.pb( + gcdc_entity_type.UpdateEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_entity_type.EntityType.to_json( + gcdc_entity_type.EntityType() + ) + + request = gcdc_entity_type.UpdateEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_entity_type.EntityType() + + client.update_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_entity_type_rest_bad_request( + transport: str = "rest", request_type=gcdc_entity_type.UpdateEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + request_init["entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_entity_type(request) + + +def test_update_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + entity_type=gcdc_entity_type.EntityType(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{entity_type.name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_entity_type( + gcdc_entity_type.UpdateEntityTypeRequest(), + entity_type=gcdc_entity_type.EntityType(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + entity_type.DeleteEntityTypeRequest, + dict, + ], +) +def test_delete_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_entity_type(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_entity_type_rest_required_fields( + request_type=entity_type.DeleteEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_delete_entity_type" + ) as pre: + pre.assert_not_called() + pb_message = entity_type.DeleteEntityTypeRequest.pb( + entity_type.DeleteEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = entity_type.DeleteEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_entity_type_rest_bad_request( + transport: str = "rest", request_type=entity_type.DeleteEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_entity_type(request) + + +def test_delete_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_entity_type( + entity_type.DeleteEntityTypeRequest(), + name="name_value", + ) + + +def test_delete_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = EntityTypesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.EntityTypesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.EntityTypesGrpcTransport, + transports.EntityTypesGrpcAsyncIOTransport, + transports.EntityTypesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = EntityTypesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.EntityTypesGrpcTransport, + ) + + +def test_entity_types_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.EntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_entity_types_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.EntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_entity_types", + "get_entity_type", + "create_entity_type", + "update_entity_type", + "delete_entity_type", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_entity_types_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.EntityTypesTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_entity_types_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.EntityTypesTransport() + adc.assert_called_once() + + +def test_entity_types_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + EntityTypesClient() + adc.assert_called_once_with( scopes=None, default_scopes=( "https://www.googleapis.com/auth/cloud-platform", @@ -2406,6 +3972,7 @@ def test_entity_types_transport_auth_adc(transport_class): [ transports.EntityTypesGrpcTransport, transports.EntityTypesGrpcAsyncIOTransport, + transports.EntityTypesRestTransport, ], ) def test_entity_types_transport_auth_gdch_credentials(transport_class): @@ -2503,11 +4070,23 @@ def test_entity_types_grpc_transport_client_cert_source_for_mtls(transport_class ) +def test_entity_types_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.EntityTypesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_entity_types_host_no_port(transport_name): @@ -2518,7 +4097,11 @@ def test_entity_types_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2526,6 +4109,7 @@ def test_entity_types_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_entity_types_host_with_port(transport_name): @@ -2536,7 +4120,45 @@ def test_entity_types_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_entity_types_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = EntityTypesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = EntityTypesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_entity_types._session + session2 = client2.transport.list_entity_types._session + assert session1 != session2 + session1 = client1.transport.get_entity_type._session + session2 = client2.transport.get_entity_type._session + assert session1 != session2 + session1 = client1.transport.create_entity_type._session + session2 = client2.transport.create_entity_type._session + assert session1 != session2 + session1 = client1.transport.update_entity_type._session + session2 = client2.transport.update_entity_type._session + assert session1 != session2 + session1 = client1.transport.delete_entity_type._session + session2 = client2.transport.delete_entity_type._session + assert session1 != session2 def test_entity_types_grpc_transport_channel(): @@ -2826,6 +4448,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3543,6 +5451,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3560,6 +5469,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_environments.py b/tests/unit/gapic/dialogflowcx_v3/test_environments.py index 040f0286..a185b741 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_environments.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_environments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -104,6 +111,7 @@ def test__get_default_mtls_endpoint(): [ (EnvironmentsClient, "grpc"), (EnvironmentsAsyncClient, "grpc_asyncio"), + (EnvironmentsClient, "rest"), ], ) def test_environments_client_from_service_account_info(client_class, transport_name): @@ -117,7 +125,11 @@ def test_environments_client_from_service_account_info(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -125,6 +137,7 @@ def test_environments_client_from_service_account_info(client_class, transport_n [ (transports.EnvironmentsGrpcTransport, "grpc"), (transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.EnvironmentsRestTransport, "rest"), ], ) def test_environments_client_service_account_always_use_jwt( @@ -150,6 +163,7 @@ def test_environments_client_service_account_always_use_jwt( [ (EnvironmentsClient, "grpc"), (EnvironmentsAsyncClient, "grpc_asyncio"), + (EnvironmentsClient, "rest"), ], ) def test_environments_client_from_service_account_file(client_class, transport_name): @@ -170,13 +184,18 @@ def test_environments_client_from_service_account_file(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_environments_client_get_transport_class(): transport = EnvironmentsClient.get_transport_class() available_transports = [ transports.EnvironmentsGrpcTransport, + transports.EnvironmentsRestTransport, ] assert transport in available_transports @@ -193,6 +212,7 @@ def test_environments_client_get_transport_class(): transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -336,6 +356,8 @@ def test_environments_client_client_options( "grpc_asyncio", "false", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", "true"), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -529,6 +551,7 @@ def test_environments_client_get_mtls_endpoint_and_cert_source(client_class): transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest"), ], ) def test_environments_client_client_options_scopes( @@ -569,6 +592,7 @@ def test_environments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", None), ], ) def test_environments_client_client_options_credentials_file( @@ -3284,141 +3308,2859 @@ async def test_deploy_flow_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.EnvironmentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + environment.ListEnvironmentsRequest, + dict, + ], +) +def test_list_environments_rest(request_type): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_environments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListEnvironmentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_environments_rest_required_fields( + request_type=environment.ListEnvironmentsRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_environments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_environments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_environments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_environments_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + + unset_fields = transport.list_environments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_environments_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options=options, - transport=transport, + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_list_environments" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_list_environments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.ListEnvironmentsRequest.pb( + environment.ListEnvironmentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = environment.ListEnvironmentsResponse.to_json( + environment.ListEnvironmentsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = environment.ListEnvironmentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.ListEnvironmentsResponse() + + client.list_environments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_environments_rest_bad_request( + transport: str = "rest", request_type=environment.ListEnvironmentsRequest +): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.EnvironmentsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_environments(request) + + +def test_list_environments_rest_flattened(): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = EnvironmentsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.EnvironmentsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_environments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/environments" + % client.transport._host, + args[1], + ) + + +def test_list_environments_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.EnvironmentsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_environments( + environment.ListEnvironmentsRequest(), + parent="parent_value", + ) + + +def test_list_environments_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + environment.Environment(), + environment.Environment(), + ], + next_page_token="abc", + ), + environment.ListEnvironmentsResponse( + environments=[], + next_page_token="def", + ), + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + ], + next_page_token="ghi", + ), + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + environment.Environment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.ListEnvironmentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.EnvironmentsGrpcTransport, - transports.EnvironmentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_environments(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.Environment) for i in results) + + pages = list(client.list_environments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + environment.GetEnvironmentRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = EnvironmentsClient.get_transport_class(transport_name)( +def test_get_environment_rest(request_type): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.Environment( + name="name_value", + display_name="display_name_value", + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_environment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, environment.Environment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + + +def test_get_environment_rest_required_fields( + request_type=environment.GetEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.EnvironmentsGrpcTransport, + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.Environment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_environment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) -def test_environments_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.EnvironmentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_get_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_get_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.GetEnvironmentRequest.pb( + environment.GetEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = environment.Environment.to_json( + environment.Environment() ) + request = environment.GetEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.Environment() -def test_environments_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.environments.transports.EnvironmentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.EnvironmentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_environment_rest_bad_request( + transport: str = "rest", request_type=environment.GetEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_environment(request) + + +def test_get_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.Environment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_environment( + environment.GetEnvironmentRequest(), + name="name_value", + ) + + +def test_get_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_environment.CreateEnvironmentRequest, + dict, + ], +) +def test_create_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["environment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_environment(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_create_environment_rest_required_fields( + request_type=gcdc_environment.CreateEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_environment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "environment", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_create_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_create_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_environment.CreateEnvironmentRequest.pb( + gcdc_environment.CreateEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_environment.CreateEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.create_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_environment_rest_bad_request( + transport: str = "rest", request_type=gcdc_environment.CreateEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["environment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_environment(request) + + +def test_create_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + environment=gcdc_environment.Environment(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/environments" + % client.transport._host, + args[1], + ) + + +def test_create_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_environment( + gcdc_environment.CreateEnvironmentRequest(), + parent="parent_value", + environment=gcdc_environment.Environment(name="name_value"), + ) + + +def test_create_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_environment.UpdateEnvironmentRequest, + dict, + ], +) +def test_update_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + request_init["environment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_environment(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_update_environment_rest_required_fields( + request_type=gcdc_environment.UpdateEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_environment._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_environment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "environment", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_update_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_update_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_environment.UpdateEnvironmentRequest.pb( + gcdc_environment.UpdateEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_environment.UpdateEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.update_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_environment_rest_bad_request( + transport: str = "rest", request_type=gcdc_environment.UpdateEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + request_init["environment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_environment(request) + + +def test_update_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + environment=gcdc_environment.Environment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{environment.name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_update_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_environment( + gcdc_environment.UpdateEnvironmentRequest(), + environment=gcdc_environment.Environment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.DeleteEnvironmentRequest, + dict, + ], +) +def test_delete_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_environment(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_environment_rest_required_fields( + request_type=environment.DeleteEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_environment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_delete_environment" + ) as pre: + pre.assert_not_called() + pb_message = environment.DeleteEnvironmentRequest.pb( + environment.DeleteEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = environment.DeleteEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_environment_rest_bad_request( + transport: str = "rest", request_type=environment.DeleteEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_environment(request) + + +def test_delete_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_environment( + environment.DeleteEnvironmentRequest(), + name="name_value", + ) + + +def test_delete_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.LookupEnvironmentHistoryRequest, + dict, + ], +) +def test_lookup_environment_history_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.lookup_environment_history(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.LookupEnvironmentHistoryPager) + assert response.next_page_token == "next_page_token_value" + + +def test_lookup_environment_history_rest_required_fields( + request_type=environment.LookupEnvironmentHistoryRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).lookup_environment_history._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).lookup_environment_history._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.lookup_environment_history(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_lookup_environment_history_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.lookup_environment_history._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("name",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_lookup_environment_history_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_lookup_environment_history" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_lookup_environment_history" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.LookupEnvironmentHistoryRequest.pb( + environment.LookupEnvironmentHistoryRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + environment.LookupEnvironmentHistoryResponse.to_json( + environment.LookupEnvironmentHistoryResponse() + ) + ) + + request = environment.LookupEnvironmentHistoryRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.LookupEnvironmentHistoryResponse() + + client.lookup_environment_history( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_lookup_environment_history_rest_bad_request( + transport: str = "rest", request_type=environment.LookupEnvironmentHistoryRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.lookup_environment_history(request) + + +def test_lookup_environment_history_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.lookup_environment_history(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*}:lookupEnvironmentHistory" + % client.transport._host, + args[1], + ) + + +def test_lookup_environment_history_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.lookup_environment_history( + environment.LookupEnvironmentHistoryRequest(), + name="name_value", + ) + + +def test_lookup_environment_history_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + environment.Environment(), + environment.Environment(), + ], + next_page_token="abc", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[], + next_page_token="def", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + ], + next_page_token="ghi", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + environment.Environment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.LookupEnvironmentHistoryResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.lookup_environment_history(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.Environment) for i in results) + + pages = list(client.lookup_environment_history(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + environment.RunContinuousTestRequest, + dict, + ], +) +def test_run_continuous_test_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.run_continuous_test(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_run_continuous_test_rest_required_fields( + request_type=environment.RunContinuousTestRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["environment"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_continuous_test._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["environment"] = "environment_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_continuous_test._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "environment" in jsonified_request + assert jsonified_request["environment"] == "environment_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.run_continuous_test(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_run_continuous_test_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.run_continuous_test._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("environment",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_run_continuous_test_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_run_continuous_test" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_run_continuous_test" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.RunContinuousTestRequest.pb( + environment.RunContinuousTestRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = environment.RunContinuousTestRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.run_continuous_test( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_run_continuous_test_rest_bad_request( + transport: str = "rest", request_type=environment.RunContinuousTestRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.run_continuous_test(request) + + +def test_run_continuous_test_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.ListContinuousTestResultsRequest, + dict, + ], +) +def test_list_continuous_test_results_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListContinuousTestResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_continuous_test_results(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListContinuousTestResultsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_continuous_test_results_rest_required_fields( + request_type=environment.ListContinuousTestResultsRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_continuous_test_results._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_continuous_test_results._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.ListContinuousTestResultsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_continuous_test_results(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_continuous_test_results_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_continuous_test_results._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_continuous_test_results_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_list_continuous_test_results" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_list_continuous_test_results" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.ListContinuousTestResultsRequest.pb( + environment.ListContinuousTestResultsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + environment.ListContinuousTestResultsResponse.to_json( + environment.ListContinuousTestResultsResponse() + ) + ) + + request = environment.ListContinuousTestResultsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.ListContinuousTestResultsResponse() + + client.list_continuous_test_results( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_continuous_test_results_rest_bad_request( + transport: str = "rest", request_type=environment.ListContinuousTestResultsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_continuous_test_results(request) + + +def test_list_continuous_test_results_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListContinuousTestResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_continuous_test_results(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/environments/*}/continuousTestResults" + % client.transport._host, + args[1], + ) + + +def test_list_continuous_test_results_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_continuous_test_results( + environment.ListContinuousTestResultsRequest(), + parent="parent_value", + ) + + +def test_list_continuous_test_results_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + ], + next_page_token="abc", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[], + next_page_token="def", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + ], + next_page_token="ghi", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.ListContinuousTestResultsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.list_continuous_test_results(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.ContinuousTestResult) for i in results) + + pages = list(client.list_continuous_test_results(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + environment.DeployFlowRequest, + dict, + ], +) +def test_deploy_flow_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.deploy_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_deploy_flow_rest_required_fields(request_type=environment.DeployFlowRequest): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["environment"] = "" + request_init["flow_version"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).deploy_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["environment"] = "environment_value" + jsonified_request["flowVersion"] = "flow_version_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).deploy_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "environment" in jsonified_request + assert jsonified_request["environment"] == "environment_value" + assert "flowVersion" in jsonified_request + assert jsonified_request["flowVersion"] == "flow_version_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.deploy_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_deploy_flow_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.deploy_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "environment", + "flowVersion", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_deploy_flow_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_deploy_flow" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_deploy_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.DeployFlowRequest.pb(environment.DeployFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = environment.DeployFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.deploy_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_deploy_flow_rest_bad_request( + transport: str = "rest", request_type=environment.DeployFlowRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.deploy_flow(request) + + +def test_deploy_flow_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = EnvironmentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.EnvironmentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.EnvironmentsGrpcTransport, + transports.EnvironmentsGrpcAsyncIOTransport, + transports.EnvironmentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = EnvironmentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.EnvironmentsGrpcTransport, + ) + + +def test_environments_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.EnvironmentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_environments_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.environments.transports.EnvironmentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.EnvironmentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_environments", @@ -3535,6 +6277,7 @@ def test_environments_transport_auth_adc(transport_class): [ transports.EnvironmentsGrpcTransport, transports.EnvironmentsGrpcAsyncIOTransport, + transports.EnvironmentsRestTransport, ], ) def test_environments_transport_auth_gdch_credentials(transport_class): @@ -3632,11 +6375,40 @@ def test_environments_grpc_transport_client_cert_source_for_mtls(transport_class ) +def test_environments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.EnvironmentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_environments_rest_lro_client(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_environments_host_no_port(transport_name): @@ -3647,7 +6419,11 @@ def test_environments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3655,6 +6431,7 @@ def test_environments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_environments_host_with_port(transport_name): @@ -3665,7 +6442,57 @@ def test_environments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_environments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = EnvironmentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = EnvironmentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_environments._session + session2 = client2.transport.list_environments._session + assert session1 != session2 + session1 = client1.transport.get_environment._session + session2 = client2.transport.get_environment._session + assert session1 != session2 + session1 = client1.transport.create_environment._session + session2 = client2.transport.create_environment._session + assert session1 != session2 + session1 = client1.transport.update_environment._session + session2 = client2.transport.update_environment._session + assert session1 != session2 + session1 = client1.transport.delete_environment._session + session2 = client2.transport.delete_environment._session + assert session1 != session2 + session1 = client1.transport.lookup_environment_history._session + session2 = client2.transport.lookup_environment_history._session + assert session1 != session2 + session1 = client1.transport.run_continuous_test._session + session2 = client2.transport.run_continuous_test._session + assert session1 != session2 + session1 = client1.transport.list_continuous_test_results._session + session2 = client2.transport.list_continuous_test_results._session + assert session1 != session2 + session1 = client1.transport.deploy_flow._session + session2 = client2.transport.deploy_flow._session + assert session1 != session2 def test_environments_grpc_transport_channel(): @@ -4176,6 +7003,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4893,6 +8006,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4910,6 +8024,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_experiments.py b/tests/unit/gapic/dialogflowcx_v3/test_experiments.py index a8d2cbf5..c31364ae 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_experiments.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_experiments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -98,6 +105,7 @@ def test__get_default_mtls_endpoint(): [ (ExperimentsClient, "grpc"), (ExperimentsAsyncClient, "grpc_asyncio"), + (ExperimentsClient, "rest"), ], ) def test_experiments_client_from_service_account_info(client_class, transport_name): @@ -111,7 +119,11 @@ def test_experiments_client_from_service_account_info(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -119,6 +131,7 @@ def test_experiments_client_from_service_account_info(client_class, transport_na [ (transports.ExperimentsGrpcTransport, "grpc"), (transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ExperimentsRestTransport, "rest"), ], ) def test_experiments_client_service_account_always_use_jwt( @@ -144,6 +157,7 @@ def test_experiments_client_service_account_always_use_jwt( [ (ExperimentsClient, "grpc"), (ExperimentsAsyncClient, "grpc_asyncio"), + (ExperimentsClient, "rest"), ], ) def test_experiments_client_from_service_account_file(client_class, transport_name): @@ -164,13 +178,18 @@ def test_experiments_client_from_service_account_file(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_experiments_client_get_transport_class(): transport = ExperimentsClient.get_transport_class() available_transports = [ transports.ExperimentsGrpcTransport, + transports.ExperimentsRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_experiments_client_get_transport_class(): transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -330,6 +350,8 @@ def test_experiments_client_client_options( "grpc_asyncio", "false", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", "true"), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -523,6 +545,7 @@ def test_experiments_client_get_mtls_endpoint_and_cert_source(client_class): transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest"), ], ) def test_experiments_client_client_options_scopes( @@ -558,6 +581,7 @@ def test_experiments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", None), ], ) def test_experiments_client_client_options_credentials_file( @@ -2610,209 +2634,2511 @@ async def test_stop_experiment_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.ExperimentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + experiment.ListExperimentsRequest, + dict, + ], +) +def test_list_experiments_rest(request_type): + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = ExperimentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_experiments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListExperimentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_experiments_rest_required_fields( + request_type=experiment.ListExperimentsRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_experiments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_experiments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.ExperimentsGrpcTransport( + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = ExperimentsClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_experiments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_experiments_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.ExperimentsGrpcAsyncIOTransport( + unset_fields = transport.list_experiments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_experiments_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_list_experiments" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_list_experiments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.ListExperimentsRequest.pb( + experiment.ListExperimentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.ListExperimentsResponse.to_json( + experiment.ListExperimentsResponse() + ) + request = experiment.ListExperimentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.ListExperimentsResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.ExperimentsGrpcTransport, - transports.ExperimentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_experiments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = ExperimentsClient.get_transport_class(transport_name)( + +def test_list_experiments_rest_bad_request( + transport: str = "rest", request_type=experiment.ListExperimentsRequest +): + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_experiments(request) + + +def test_list_experiments_rest_flattened(): client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.ExperimentsGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse() -def test_experiments_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.ExperimentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -def test_experiments_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.ExperimentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_experiments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/environments/*}/experiments" + % client.transport._host, + args[1], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_experiments", - "get_experiment", - "create_experiment", - "update_experiment", - "delete_experiment", - "start_experiment", - "stop_experiment", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + +def test_list_experiments_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_experiments( + experiment.ListExperimentsRequest(), + parent="parent_value", + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_experiments_rest_pager(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_experiments_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.ExperimentsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + experiment.Experiment(), + experiment.Experiment(), + ], + next_page_token="abc", + ), + experiment.ListExperimentsResponse( + experiments=[], + next_page_token="def", + ), + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + ], + next_page_token="ghi", + ), + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + experiment.Experiment(), + ], ), - quota_project_id="octopus", ) + # Two responses for two calls + response = response + response + # Wrap the values into proper Response objs + response = tuple( + experiment.ListExperimentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -def test_experiments_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.ExperimentsTransport() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + pager = client.list_experiments(request=sample_request) -def test_experiments_auth_adc(): - # If no credentials are provided, we should use ADC credentials. + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, experiment.Experiment) for i in results) + + pages = list(client.list_experiments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.GetExperimentRequest, + dict, + ], +) +def test_get_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_get_experiment_rest_required_fields( + request_type=experiment.GetExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_get_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_get_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.GetExperimentRequest.pb( + experiment.GetExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.GetExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.get_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.GetExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_experiment(request) + + +def test_get_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_experiment( + experiment.GetExperimentRequest(), + name="name_value", + ) + + +def test_get_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_experiment.CreateExperimentRequest, + dict, + ], +) +def test_create_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request_init["experiment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_create_experiment_rest_required_fields( + request_type=gcdc_experiment.CreateExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_experiment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "experiment", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_create_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_create_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_experiment.CreateExperimentRequest.pb( + gcdc_experiment.CreateExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_experiment.Experiment.to_json( + gcdc_experiment.Experiment() + ) + + request = gcdc_experiment.CreateExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_experiment.Experiment() + + client.create_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_experiment_rest_bad_request( + transport: str = "rest", request_type=gcdc_experiment.CreateExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request_init["experiment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_experiment(request) + + +def test_create_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + experiment=gcdc_experiment.Experiment(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/environments/*}/experiments" + % client.transport._host, + args[1], + ) + + +def test_create_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_experiment( + gcdc_experiment.CreateExperimentRequest(), + parent="parent_value", + experiment=gcdc_experiment.Experiment(name="name_value"), + ) + + +def test_create_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_experiment.UpdateExperimentRequest, + dict, + ], +) +def test_update_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + request_init["experiment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_update_experiment_rest_required_fields( + request_type=gcdc_experiment.UpdateExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_experiment._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_experiment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "experiment", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_update_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_update_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_experiment.UpdateExperimentRequest.pb( + gcdc_experiment.UpdateExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_experiment.Experiment.to_json( + gcdc_experiment.Experiment() + ) + + request = gcdc_experiment.UpdateExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_experiment.Experiment() + + client.update_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_experiment_rest_bad_request( + transport: str = "rest", request_type=gcdc_experiment.UpdateExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + request_init["experiment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_experiment(request) + + +def test_update_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + experiment=gcdc_experiment.Experiment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{experiment.name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_update_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_experiment( + gcdc_experiment.UpdateExperimentRequest(), + experiment=gcdc_experiment.Experiment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.DeleteExperimentRequest, + dict, + ], +) +def test_delete_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_experiment(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_experiment_rest_required_fields( + request_type=experiment.DeleteExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_delete_experiment" + ) as pre: + pre.assert_not_called() + pb_message = experiment.DeleteExperimentRequest.pb( + experiment.DeleteExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = experiment.DeleteExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.DeleteExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_experiment(request) + + +def test_delete_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_experiment( + experiment.DeleteExperimentRequest(), + name="name_value", + ) + + +def test_delete_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.StartExperimentRequest, + dict, + ], +) +def test_start_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.start_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_start_experiment_rest_required_fields( + request_type=experiment.StartExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).start_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).start_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.start_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_start_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.start_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_start_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_start_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_start_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.StartExperimentRequest.pb( + experiment.StartExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.StartExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.start_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_start_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.StartExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.start_experiment(request) + + +def test_start_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.start_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:start" + % client.transport._host, + args[1], + ) + + +def test_start_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.start_experiment( + experiment.StartExperimentRequest(), + name="name_value", + ) + + +def test_start_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.StopExperimentRequest, + dict, + ], +) +def test_stop_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.stop_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_stop_experiment_rest_required_fields( + request_type=experiment.StopExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).stop_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).stop_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.stop_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_stop_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.stop_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_stop_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_stop_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_stop_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.StopExperimentRequest.pb( + experiment.StopExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.StopExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.stop_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_stop_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.StopExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.stop_experiment(request) + + +def test_stop_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.stop_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:stop" + % client.transport._host, + args[1], + ) + + +def test_stop_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.stop_experiment( + experiment.StopExperimentRequest(), + name="name_value", + ) + + +def test_stop_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = ExperimentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.ExperimentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ExperimentsGrpcTransport, + transports.ExperimentsGrpcAsyncIOTransport, + transports.ExperimentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = ExperimentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.ExperimentsGrpcTransport, + ) + + +def test_experiments_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.ExperimentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_experiments_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.ExperimentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_experiments", + "get_experiment", + "create_experiment", + "update_experiment", + "delete_experiment", + "start_experiment", + "stop_experiment", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_experiments_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ExperimentsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_experiments_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ExperimentsTransport() + adc.assert_called_once() + + +def test_experiments_auth_adc(): + # If no credentials are provided, we should use ADC credentials. with mock.patch.object(google.auth, "default", autospec=True) as adc: adc.return_value = (ga_credentials.AnonymousCredentials(), None) ExperimentsClient() @@ -2854,6 +5180,7 @@ def test_experiments_transport_auth_adc(transport_class): [ transports.ExperimentsGrpcTransport, transports.ExperimentsGrpcAsyncIOTransport, + transports.ExperimentsRestTransport, ], ) def test_experiments_transport_auth_gdch_credentials(transport_class): @@ -2951,11 +5278,23 @@ def test_experiments_grpc_transport_client_cert_source_for_mtls(transport_class) ) +def test_experiments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ExperimentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_experiments_host_no_port(transport_name): @@ -2966,7 +5305,11 @@ def test_experiments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2974,6 +5317,7 @@ def test_experiments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_experiments_host_with_port(transport_name): @@ -2984,7 +5328,51 @@ def test_experiments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_experiments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ExperimentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ExperimentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_experiments._session + session2 = client2.transport.list_experiments._session + assert session1 != session2 + session1 = client1.transport.get_experiment._session + session2 = client2.transport.get_experiment._session + assert session1 != session2 + session1 = client1.transport.create_experiment._session + session2 = client2.transport.create_experiment._session + assert session1 != session2 + session1 = client1.transport.update_experiment._session + session2 = client2.transport.update_experiment._session + assert session1 != session2 + session1 = client1.transport.delete_experiment._session + session2 = client2.transport.delete_experiment._session + assert session1 != session2 + session1 = client1.transport.start_experiment._session + session2 = client2.transport.start_experiment._session + assert session1 != session2 + session1 = client1.transport.stop_experiment._session + session2 = client2.transport.stop_experiment._session + assert session1 != session2 def test_experiments_grpc_transport_channel(): @@ -3311,6 +5699,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4028,6 +6702,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4045,6 +6720,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_flows.py b/tests/unit/gapic/dialogflowcx_v3/test_flows.py index b9d7753b..3d8ae30b 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_flows.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_flows.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -104,6 +111,7 @@ def test__get_default_mtls_endpoint(): [ (FlowsClient, "grpc"), (FlowsAsyncClient, "grpc_asyncio"), + (FlowsClient, "rest"), ], ) def test_flows_client_from_service_account_info(client_class, transport_name): @@ -117,7 +125,11 @@ def test_flows_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -125,6 +137,7 @@ def test_flows_client_from_service_account_info(client_class, transport_name): [ (transports.FlowsGrpcTransport, "grpc"), (transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.FlowsRestTransport, "rest"), ], ) def test_flows_client_service_account_always_use_jwt(transport_class, transport_name): @@ -148,6 +161,7 @@ def test_flows_client_service_account_always_use_jwt(transport_class, transport_ [ (FlowsClient, "grpc"), (FlowsAsyncClient, "grpc_asyncio"), + (FlowsClient, "rest"), ], ) def test_flows_client_from_service_account_file(client_class, transport_name): @@ -168,13 +182,18 @@ def test_flows_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_flows_client_get_transport_class(): transport = FlowsClient.get_transport_class() available_transports = [ transports.FlowsGrpcTransport, + transports.FlowsRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_flows_client_get_transport_class(): [ (FlowsClient, transports.FlowsGrpcTransport, "grpc"), (FlowsAsyncClient, transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (FlowsClient, transports.FlowsRestTransport, "rest"), ], ) @mock.patch.object( @@ -326,6 +346,8 @@ def test_flows_client_client_options(client_class, transport_class, transport_na "grpc_asyncio", "false", ), + (FlowsClient, transports.FlowsRestTransport, "rest", "true"), + (FlowsClient, transports.FlowsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -511,6 +533,7 @@ def test_flows_client_get_mtls_endpoint_and_cert_source(client_class): [ (FlowsClient, transports.FlowsGrpcTransport, "grpc"), (FlowsAsyncClient, transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (FlowsClient, transports.FlowsRestTransport, "rest"), ], ) def test_flows_client_client_options_scopes( @@ -546,6 +569,7 @@ def test_flows_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (FlowsClient, transports.FlowsRestTransport, "rest", None), ], ) def test_flows_client_client_options_credentials_file( @@ -2944,209 +2968,3187 @@ async def test_export_flow_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.FlowsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + gcdc_flow.CreateFlowRequest, + dict, + ], +) +def test_create_flow_rest(request_type): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = FlowsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["flow"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.FlowsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = FlowsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_create_flow_rest_required_fields(request_type=gcdc_flow.CreateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.FlowsGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = FlowsClient( - client_options=options, - transport=transport, - ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = FlowsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + unset_fields = transport.create_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "flow", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.FlowsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), ) - with pytest.raises(ValueError): - client = FlowsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_create_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_create_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_flow.CreateFlowRequest.pb(gcdc_flow.CreateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_flow.Flow.to_json(gcdc_flow.Flow()) + + request = gcdc_flow.CreateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_flow.Flow() + + client.create_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.FlowsGrpcTransport( + +def test_create_flow_rest_bad_request( + transport: str = "rest", request_type=gcdc_flow.CreateFlowRequest +): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = FlowsClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["flow"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.FlowsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_flow(request) + + +def test_create_flow_rest_flattened(): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.FlowsGrpcAsyncIOTransport( + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + flow=gcdc_flow.Flow(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/flows" + % client.transport._host, + args[1], + ) + + +def test_create_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_flow( + gcdc_flow.CreateFlowRequest(), + parent="parent_value", + flow=gcdc_flow.Flow(name="name_value"), + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.FlowsGrpcTransport, - transports.FlowsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + +def test_create_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + flow.DeleteFlowRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = FlowsClient.get_transport_class(transport_name)( +def test_delete_flow_rest(request_type): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = FlowsClient( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.FlowsGrpcTransport, - ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" -def test_flows_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.FlowsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_flow(request) + # Establish that the response is the type that we expect. + assert response is None -def test_flows_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.FlowsTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "create_flow", - "delete_flow", - "list_flows", - "get_flow", - "update_flow", - "train_flow", - "validate_flow", - "get_flow_validation_result", - "import_flow", - "export_flow", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", +def test_delete_flow_rest_required_fields(request_type=flow.DeleteFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Additionally, the LRO client (a property) should - # also raise NotImplementedError - with pytest.raises(NotImplementedError): - transport.operations_client + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # verify required fields with default values are now present + jsonified_request["name"] = "name_value" -def test_flows_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.FlowsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", - ) + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_flows_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport._prep_wrapped_messages" - ) as Transport: + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "pre_delete_flow" + ) as pre: + pre.assert_not_called() + pb_message = flow.DeleteFlowRequest.pb(flow.DeleteFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = flow.DeleteFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_flow_rest_bad_request( + transport: str = "rest", request_type=flow.DeleteFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_flow(request) + + +def test_delete_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_flow( + flow.DeleteFlowRequest(), + name="name_value", + ) + + +def test_delete_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ListFlowsRequest, + dict, + ], +) +def test_list_flows_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_flows(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListFlowsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_flows_rest_required_fields(request_type=flow.ListFlowsRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_flows._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_flows._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_flows(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_flows_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_flows._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_flows_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_list_flows" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_list_flows" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ListFlowsRequest.pb(flow.ListFlowsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.ListFlowsResponse.to_json( + flow.ListFlowsResponse() + ) + + request = flow.ListFlowsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.ListFlowsResponse() + + client.list_flows( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_flows_rest_bad_request( + transport: str = "rest", request_type=flow.ListFlowsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_flows(request) + + +def test_list_flows_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_flows(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/flows" + % client.transport._host, + args[1], + ) + + +def test_list_flows_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_flows( + flow.ListFlowsRequest(), + parent="parent_value", + ) + + +def test_list_flows_rest_pager(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + flow.Flow(), + flow.Flow(), + ], + next_page_token="abc", + ), + flow.ListFlowsResponse( + flows=[], + next_page_token="def", + ), + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + ], + next_page_token="ghi", + ), + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + flow.Flow(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(flow.ListFlowsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_flows(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, flow.Flow) for i in results) + + pages = list(client.list_flows(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + flow.GetFlowRequest, + dict, + ], +) +def test_get_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_get_flow_rest_required_fields(request_type=flow.GetFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_get_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_get_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.GetFlowRequest.pb(flow.GetFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.Flow.to_json(flow.Flow()) + + request = flow.GetFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.Flow() + + client.get_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_flow_rest_bad_request( + transport: str = "rest", request_type=flow.GetFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_flow(request) + + +def test_get_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_get_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_flow( + flow.GetFlowRequest(), + name="name_value", + ) + + +def test_get_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_flow.UpdateFlowRequest, + dict, + ], +) +def test_update_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + request_init["flow"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_update_flow_rest_required_fields(request_type=gcdc_flow.UpdateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("flow",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_update_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_update_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_flow.UpdateFlowRequest.pb(gcdc_flow.UpdateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_flow.Flow.to_json(gcdc_flow.Flow()) + + request = gcdc_flow.UpdateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_flow.Flow() + + client.update_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_flow_rest_bad_request( + transport: str = "rest", request_type=gcdc_flow.UpdateFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + request_init["flow"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_flow(request) + + +def test_update_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + flow=gcdc_flow.Flow(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{flow.name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_update_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_flow( + gcdc_flow.UpdateFlowRequest(), + flow=gcdc_flow.Flow(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.TrainFlowRequest, + dict, + ], +) +def test_train_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.train_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_train_flow_rest_required_fields(request_type=flow.TrainFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).train_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).train_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.train_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_train_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.train_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_train_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_train_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_train_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.TrainFlowRequest.pb(flow.TrainFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.TrainFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.train_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_train_flow_rest_bad_request( + transport: str = "rest", request_type=flow.TrainFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.train_flow(request) + + +def test_train_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.train_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*}:train" + % client.transport._host, + args[1], + ) + + +def test_train_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.train_flow( + flow.TrainFlowRequest(), + name="name_value", + ) + + +def test_train_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ValidateFlowRequest, + dict, + ], +) +def test_validate_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.FlowValidationResult) + assert response.name == "name_value" + + +def test_validate_flow_rest_required_fields(request_type=flow.ValidateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_validate_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_validate_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ValidateFlowRequest.pb(flow.ValidateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.FlowValidationResult.to_json( + flow.FlowValidationResult() + ) + + request = flow.ValidateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.FlowValidationResult() + + client.validate_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ValidateFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_flow(request) + + +def test_validate_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.GetFlowValidationResultRequest, + dict, + ], +) +def test_get_flow_validation_result_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_flow_validation_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.FlowValidationResult) + assert response.name == "name_value" + + +def test_get_flow_validation_result_rest_required_fields( + request_type=flow.GetFlowValidationResultRequest, +): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow_validation_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow_validation_result._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_flow_validation_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_flow_validation_result_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_flow_validation_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_flow_validation_result_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_get_flow_validation_result" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_get_flow_validation_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.GetFlowValidationResultRequest.pb( + flow.GetFlowValidationResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.FlowValidationResult.to_json( + flow.FlowValidationResult() + ) + + request = flow.GetFlowValidationResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.FlowValidationResult() + + client.get_flow_validation_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_flow_validation_result_rest_bad_request( + transport: str = "rest", request_type=flow.GetFlowValidationResultRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_flow_validation_result(request) + + +def test_get_flow_validation_result_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_flow_validation_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/validationResult}" + % client.transport._host, + args[1], + ) + + +def test_get_flow_validation_result_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_flow_validation_result( + flow.GetFlowValidationResultRequest(), + name="name_value", + ) + + +def test_get_flow_validation_result_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ImportFlowRequest, + dict, + ], +) +def test_import_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.import_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_import_flow_rest_required_fields(request_type=flow.ImportFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.import_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_import_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.import_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_import_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_import_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_import_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ImportFlowRequest.pb(flow.ImportFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.ImportFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.import_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_import_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ImportFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.import_flow(request) + + +def test_import_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ExportFlowRequest, + dict, + ], +) +def test_export_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_flow_rest_required_fields(request_type=flow.ExportFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_export_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_export_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ExportFlowRequest.pb(flow.ExportFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.ExportFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ExportFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_flow(request) + + +def test_export_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = FlowsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = FlowsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = FlowsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.FlowsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.FlowsGrpcTransport, + transports.FlowsGrpcAsyncIOTransport, + transports.FlowsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = FlowsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.FlowsGrpcTransport, + ) + + +def test_flows_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.FlowsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_flows_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.FlowsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_flow", + "delete_flow", + "list_flows", + "get_flow", + "update_flow", + "train_flow", + "validate_flow", + "get_flow_validation_result", + "import_flow", + "export_flow", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Additionally, the LRO client (a property) should + # also raise NotImplementedError + with pytest.raises(NotImplementedError): + transport.operations_client + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_flows_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.FlowsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_flows_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.flows.transports.FlowsTransport._prep_wrapped_messages" + ) as Transport: Transport.return_value = None adc.return_value = (ga_credentials.AnonymousCredentials(), None) transport = transports.FlowsTransport() @@ -3196,6 +6198,7 @@ def test_flows_transport_auth_adc(transport_class): [ transports.FlowsGrpcTransport, transports.FlowsGrpcAsyncIOTransport, + transports.FlowsRestTransport, ], ) def test_flows_transport_auth_gdch_credentials(transport_class): @@ -3293,11 +6296,40 @@ def test_flows_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_flows_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.FlowsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_flows_rest_lro_client(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_flows_host_no_port(transport_name): @@ -3308,7 +6340,11 @@ def test_flows_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3316,6 +6352,7 @@ def test_flows_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_flows_host_with_port(transport_name): @@ -3326,7 +6363,60 @@ def test_flows_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_flows_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = FlowsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = FlowsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_flow._session + session2 = client2.transport.create_flow._session + assert session1 != session2 + session1 = client1.transport.delete_flow._session + session2 = client2.transport.delete_flow._session + assert session1 != session2 + session1 = client1.transport.list_flows._session + session2 = client2.transport.list_flows._session + assert session1 != session2 + session1 = client1.transport.get_flow._session + session2 = client2.transport.get_flow._session + assert session1 != session2 + session1 = client1.transport.update_flow._session + session2 = client2.transport.update_flow._session + assert session1 != session2 + session1 = client1.transport.train_flow._session + session2 = client2.transport.train_flow._session + assert session1 != session2 + session1 = client1.transport.validate_flow._session + session2 = client2.transport.validate_flow._session + assert session1 != session2 + session1 = client1.transport.get_flow_validation_result._session + session2 = client2.transport.get_flow_validation_result._session + assert session1 != session2 + session1 = client1.transport.import_flow._session + session2 = client2.transport.import_flow._session + assert session1 != session2 + session1 = client1.transport.export_flow._session + session2 = client2.transport.export_flow._session + assert session1 != session2 def test_flows_grpc_transport_channel(): @@ -3801,6 +6891,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4518,6 +7894,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4535,6 +7912,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_intents.py b/tests/unit/gapic/dialogflowcx_v3/test_intents.py index e56d39c5..6b70c3c9 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_intents.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_intents.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -93,6 +100,7 @@ def test__get_default_mtls_endpoint(): [ (IntentsClient, "grpc"), (IntentsAsyncClient, "grpc_asyncio"), + (IntentsClient, "rest"), ], ) def test_intents_client_from_service_account_info(client_class, transport_name): @@ -106,7 +114,11 @@ def test_intents_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -114,6 +126,7 @@ def test_intents_client_from_service_account_info(client_class, transport_name): [ (transports.IntentsGrpcTransport, "grpc"), (transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.IntentsRestTransport, "rest"), ], ) def test_intents_client_service_account_always_use_jwt(transport_class, transport_name): @@ -137,6 +150,7 @@ def test_intents_client_service_account_always_use_jwt(transport_class, transpor [ (IntentsClient, "grpc"), (IntentsAsyncClient, "grpc_asyncio"), + (IntentsClient, "rest"), ], ) def test_intents_client_from_service_account_file(client_class, transport_name): @@ -157,13 +171,18 @@ def test_intents_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_intents_client_get_transport_class(): transport = IntentsClient.get_transport_class() available_transports = [ transports.IntentsGrpcTransport, + transports.IntentsRestTransport, ] assert transport in available_transports @@ -176,6 +195,7 @@ def test_intents_client_get_transport_class(): [ (IntentsClient, transports.IntentsGrpcTransport, "grpc"), (IntentsAsyncClient, transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (IntentsClient, transports.IntentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -315,6 +335,8 @@ def test_intents_client_client_options(client_class, transport_class, transport_ "grpc_asyncio", "false", ), + (IntentsClient, transports.IntentsRestTransport, "rest", "true"), + (IntentsClient, transports.IntentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -500,6 +522,7 @@ def test_intents_client_get_mtls_endpoint_and_cert_source(client_class): [ (IntentsClient, transports.IntentsGrpcTransport, "grpc"), (IntentsAsyncClient, transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (IntentsClient, transports.IntentsRestTransport, "rest"), ], ) def test_intents_client_client_options_scopes( @@ -535,6 +558,7 @@ def test_intents_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (IntentsClient, transports.IntentsRestTransport, "rest", None), ], ) def test_intents_client_client_options_credentials_file( @@ -2035,141 +2059,1682 @@ async def test_delete_intent_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.IntentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + intent.ListIntentsRequest, + dict, + ], +) +def test_list_intents_rest(request_type): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = IntentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.IntentsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_intents(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListIntentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_intents_rest_required_fields(request_type=intent.ListIntentsRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_intents._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_intents._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "intent_view", + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = IntentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_intents(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_intents_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_intents._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "intentView", + "languageCode", + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.IntentsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_intents_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = IntentsClient( - client_options=options, - transport=transport, + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_list_intents" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_list_intents" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = intent.ListIntentsRequest.pb(intent.ListIntentsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = intent.ListIntentsResponse.to_json( + intent.ListIntentsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = IntentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = intent.ListIntentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = intent.ListIntentsResponse() + + client.list_intents( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.IntentsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_intents_rest_bad_request( + transport: str = "rest", request_type=intent.ListIntentsRequest +): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = IntentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.IntentsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_intents(request) + + +def test_list_intents_rest_flattened(): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = IntentsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.IntentsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_intents(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/intents" + % client.transport._host, + args[1], + ) + + +def test_list_intents_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.IntentsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_intents( + intent.ListIntentsRequest(), + parent="parent_value", + ) + + +def test_list_intents_rest_pager(transport: str = "rest"): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + intent.Intent(), + intent.Intent(), + ], + next_page_token="abc", + ), + intent.ListIntentsResponse( + intents=[], + next_page_token="def", + ), + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + ], + next_page_token="ghi", + ), + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + intent.Intent(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(intent.ListIntentsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.IntentsGrpcTransport, - transports.IntentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_intents(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, intent.Intent) for i in results) + + pages = list(client.list_intents(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + intent.GetIntentRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = IntentsClient.get_transport_class(transport_name)( +def test_get_intent_rest(request_type): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_get_intent_rest_required_fields(request_type=intent.GetIntentRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert isinstance( - client.transport, - transports.IntentsGrpcTransport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_intent._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) -def test_intents_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.IntentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_get_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_get_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = intent.GetIntentRequest.pb(intent.GetIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = intent.Intent.to_json(intent.Intent()) + + request = intent.GetIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = intent.Intent() -def test_intents_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.intents.transports.IntentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.IntentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_intent_rest_bad_request( + transport: str = "rest", request_type=intent.GetIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_intent(request) + + +def test_get_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_get_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_intent( + intent.GetIntentRequest(), + name="name_value", + ) + + +def test_get_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_intent.CreateIntentRequest, + dict, + ], +) +def test_create_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["intent"] = { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_create_intent_rest_required_fields( + request_type=gcdc_intent.CreateIntentRequest, +): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "intent", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_create_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_create_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_intent.CreateIntentRequest.pb( + gcdc_intent.CreateIntentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_intent.Intent.to_json(gcdc_intent.Intent()) + + request = gcdc_intent.CreateIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_intent.Intent() + + client.create_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_intent_rest_bad_request( + transport: str = "rest", request_type=gcdc_intent.CreateIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["intent"] = { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_intent(request) + + +def test_create_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + intent=gcdc_intent.Intent(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/intents" + % client.transport._host, + args[1], + ) + + +def test_create_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_intent( + gcdc_intent.CreateIntentRequest(), + parent="parent_value", + intent=gcdc_intent.Intent(name="name_value"), + ) + + +def test_create_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_intent.UpdateIntentRequest, + dict, + ], +) +def test_update_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + request_init["intent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_update_intent_rest_required_fields( + request_type=gcdc_intent.UpdateIntentRequest, +): + transport_class = transports.IntentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("intent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_update_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_update_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_intent.UpdateIntentRequest.pb( + gcdc_intent.UpdateIntentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_intent.Intent.to_json(gcdc_intent.Intent()) + + request = gcdc_intent.UpdateIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_intent.Intent() + + client.update_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_intent_rest_bad_request( + transport: str = "rest", request_type=gcdc_intent.UpdateIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + request_init["intent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_intent(request) + + +def test_update_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + intent=gcdc_intent.Intent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{intent.name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_update_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_intent( + gcdc_intent.UpdateIntentRequest(), + intent=gcdc_intent.Intent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + intent.DeleteIntentRequest, + dict, + ], +) +def test_delete_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_intent(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_intent_rest_required_fields(request_type=intent.DeleteIntentRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_intent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "pre_delete_intent" + ) as pre: + pre.assert_not_called() + pb_message = intent.DeleteIntentRequest.pb(intent.DeleteIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = intent.DeleteIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_intent_rest_bad_request( + transport: str = "rest", request_type=intent.DeleteIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_intent(request) + + +def test_delete_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_intent( + intent.DeleteIntentRequest(), + name="name_value", + ) + + +def test_delete_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = IntentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = IntentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = IntentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.IntentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IntentsGrpcTransport, + transports.IntentsGrpcAsyncIOTransport, + transports.IntentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = IntentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.IntentsGrpcTransport, + ) + + +def test_intents_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.IntentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_intents_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.intents.transports.IntentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.IntentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_intents", @@ -2277,6 +3842,7 @@ def test_intents_transport_auth_adc(transport_class): [ transports.IntentsGrpcTransport, transports.IntentsGrpcAsyncIOTransport, + transports.IntentsRestTransport, ], ) def test_intents_transport_auth_gdch_credentials(transport_class): @@ -2374,11 +3940,23 @@ def test_intents_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_intents_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.IntentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_intents_host_no_port(transport_name): @@ -2389,7 +3967,11 @@ def test_intents_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2397,6 +3979,7 @@ def test_intents_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_intents_host_with_port(transport_name): @@ -2407,7 +3990,45 @@ def test_intents_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_intents_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = IntentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = IntentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_intents._session + session2 = client2.transport.list_intents._session + assert session1 != session2 + session1 = client1.transport.get_intent._session + session2 = client2.transport.get_intent._session + assert session1 != session2 + session1 = client1.transport.create_intent._session + session2 = client2.transport.create_intent._session + assert session1 != session2 + session1 = client1.transport.update_intent._session + session2 = client2.transport.update_intent._session + assert session1 != session2 + session1 = client1.transport.delete_intent._session + session2 = client2.transport.delete_intent._session + assert session1 != session2 def test_intents_grpc_transport_channel(): @@ -2726,6 +4347,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3443,6 +5350,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3460,6 +5368,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_pages.py b/tests/unit/gapic/dialogflowcx_v3/test_pages.py index fd078c44..5c2b3c28 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_pages.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_pages.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -96,6 +103,7 @@ def test__get_default_mtls_endpoint(): [ (PagesClient, "grpc"), (PagesAsyncClient, "grpc_asyncio"), + (PagesClient, "rest"), ], ) def test_pages_client_from_service_account_info(client_class, transport_name): @@ -109,7 +117,11 @@ def test_pages_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -117,6 +129,7 @@ def test_pages_client_from_service_account_info(client_class, transport_name): [ (transports.PagesGrpcTransport, "grpc"), (transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.PagesRestTransport, "rest"), ], ) def test_pages_client_service_account_always_use_jwt(transport_class, transport_name): @@ -140,6 +153,7 @@ def test_pages_client_service_account_always_use_jwt(transport_class, transport_ [ (PagesClient, "grpc"), (PagesAsyncClient, "grpc_asyncio"), + (PagesClient, "rest"), ], ) def test_pages_client_from_service_account_file(client_class, transport_name): @@ -160,13 +174,18 @@ def test_pages_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_pages_client_get_transport_class(): transport = PagesClient.get_transport_class() available_transports = [ transports.PagesGrpcTransport, + transports.PagesRestTransport, ] assert transport in available_transports @@ -179,6 +198,7 @@ def test_pages_client_get_transport_class(): [ (PagesClient, transports.PagesGrpcTransport, "grpc"), (PagesAsyncClient, transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (PagesClient, transports.PagesRestTransport, "rest"), ], ) @mock.patch.object( @@ -318,6 +338,8 @@ def test_pages_client_client_options(client_class, transport_class, transport_na "grpc_asyncio", "false", ), + (PagesClient, transports.PagesRestTransport, "rest", "true"), + (PagesClient, transports.PagesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -503,6 +525,7 @@ def test_pages_client_get_mtls_endpoint_and_cert_source(client_class): [ (PagesClient, transports.PagesGrpcTransport, "grpc"), (PagesAsyncClient, transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (PagesClient, transports.PagesRestTransport, "rest"), ], ) def test_pages_client_client_options_scopes( @@ -538,6 +561,7 @@ def test_pages_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (PagesClient, transports.PagesRestTransport, "rest", None), ], ) def test_pages_client_client_options_credentials_file( @@ -2014,168 +2038,2023 @@ async def test_delete_page_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.PagesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + page.ListPagesRequest, + dict, + ], +) +def test_list_pages_rest(request_type): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = PagesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.PagesGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_pages(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListPagesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_pages_rest_required_fields(request_type=page.ListPagesRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_pages._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_pages._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = PagesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_pages(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_pages_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_pages._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.PagesGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_pages_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PagesClient( - client_options=options, - transport=transport, + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_list_pages" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_list_pages" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = page.ListPagesRequest.pb(page.ListPagesRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = page.ListPagesResponse.to_json( + page.ListPagesResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PagesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = page.ListPagesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = page.ListPagesResponse() + + client.list_pages( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.PagesGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_pages_rest_bad_request( + transport: str = "rest", request_type=page.ListPagesRequest +): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = PagesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.PagesGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_pages(request) + + +def test_list_pages_rest_flattened(): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = PagesClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.PagesGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_pages(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/pages" + % client.transport._host, + args[1], + ) + + +def test_list_pages_rest_flattened_error(transport: str = "rest"): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.PagesGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_pages( + page.ListPagesRequest(), + parent="parent_value", + ) + + +def test_list_pages_rest_pager(transport: str = "rest"): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + page.ListPagesResponse( + pages=[ + page.Page(), + page.Page(), + page.Page(), + ], + next_page_token="abc", + ), + page.ListPagesResponse( + pages=[], + next_page_token="def", + ), + page.ListPagesResponse( + pages=[ + page.Page(), + ], + next_page_token="ghi", + ), + page.ListPagesResponse( + pages=[ + page.Page(), + page.Page(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(page.ListPagesResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.PagesGrpcTransport, - transports.PagesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_pages(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, page.Page) for i in results) + + pages = list(client.list_pages(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + page.GetPageRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = PagesClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_page_rest(request_type): client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.PagesGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) -def test_pages_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.PagesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_pages_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.pages.transports.PagesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.PagesTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_page(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_pages", - "get_page", - "create_page", - "update_page", - "delete_page", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + # Establish that the response is the type that we expect. + assert isinstance(response, page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_get_page_rest_required_fields(request_type=page.GetPageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_page._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_get_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_get_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = page.GetPageRequest.pb(page.GetPageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = page.Page.to_json(page.Page()) + + request = page.GetPageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = page.Page() + + client.get_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_page_rest_bad_request( + transport: str = "rest", request_type=page.GetPageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_page(request) + + +def test_get_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_get_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_page( + page.GetPageRequest(), + name="name_value", + ) + + +def test_get_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_page.CreatePageRequest, + dict, + ], +) +def test_create_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["page"] = { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_page(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_create_page_rest_required_fields(request_type=gcdc_page.CreatePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_page._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "page", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_create_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_create_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_page.CreatePageRequest.pb(gcdc_page.CreatePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_page.Page.to_json(gcdc_page.Page()) + + request = gcdc_page.CreatePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_page.Page() + + client.create_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_page_rest_bad_request( + transport: str = "rest", request_type=gcdc_page.CreatePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["page"] = { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_page(request) + + +def test_create_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + page=gcdc_page.Page(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/pages" + % client.transport._host, + args[1], + ) + + +def test_create_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_page( + gcdc_page.CreatePageRequest(), + parent="parent_value", + page=gcdc_page.Page(name="name_value"), + ) + + +def test_create_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_page.UpdatePageRequest, + dict, + ], +) +def test_update_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + request_init["page"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_page(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_update_page_rest_required_fields(request_type=gcdc_page.UpdatePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_page._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("page",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_update_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_update_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_page.UpdatePageRequest.pb(gcdc_page.UpdatePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_page.Page.to_json(gcdc_page.Page()) + + request = gcdc_page.UpdatePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_page.Page() + + client.update_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_page_rest_bad_request( + transport: str = "rest", request_type=gcdc_page.UpdatePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + request_init["page"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_page(request) + + +def test_update_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + page=gcdc_page.Page(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{page.name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_update_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_page( + gcdc_page.UpdatePageRequest(), + page=gcdc_page.Page(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + page.DeletePageRequest, + dict, + ], +) +def test_delete_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_page(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_page_rest_required_fields(request_type=page.DeletePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_page._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "pre_delete_page" + ) as pre: + pre.assert_not_called() + pb_message = page.DeletePageRequest.pb(page.DeletePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = page.DeletePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_page_rest_bad_request( + transport: str = "rest", request_type=page.DeletePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_page(request) + + +def test_delete_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_page( + page.DeletePageRequest(), + name="name_value", + ) + + +def test_delete_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PagesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PagesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = PagesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.PagesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.PagesGrpcTransport, + transports.PagesGrpcAsyncIOTransport, + transports.PagesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = PagesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.PagesGrpcTransport, + ) + + +def test_pages_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.PagesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_pages_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.pages.transports.PagesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.PagesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_pages", + "get_page", + "create_page", + "update_page", + "delete_page", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() def test_pages_base_transport_with_credentials_file(): @@ -2256,6 +4135,7 @@ def test_pages_transport_auth_adc(transport_class): [ transports.PagesGrpcTransport, transports.PagesGrpcAsyncIOTransport, + transports.PagesRestTransport, ], ) def test_pages_transport_auth_gdch_credentials(transport_class): @@ -2353,11 +4233,23 @@ def test_pages_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_pages_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.PagesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_pages_host_no_port(transport_name): @@ -2368,7 +4260,11 @@ def test_pages_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2376,6 +4272,7 @@ def test_pages_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_pages_host_with_port(transport_name): @@ -2386,7 +4283,45 @@ def test_pages_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_pages_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = PagesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = PagesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_pages._session + session2 = client2.transport.list_pages._session + assert session1 != session2 + session1 = client1.transport.get_page._session + session2 = client2.transport.get_page._session + assert session1 != session2 + session1 = client1.transport.create_page._session + session2 = client2.transport.create_page._session + assert session1 != session2 + session1 = client1.transport.update_page._session + session2 = client2.transport.update_page._session + assert session1 != session2 + session1 = client1.transport.delete_page._session + session2 = client2.transport.delete_page._session + assert session1 != session2 def test_pages_grpc_transport_channel(): @@ -2827,6 +4762,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3544,6 +5765,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3561,6 +5783,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_security_settings_service.py b/tests/unit/gapic/dialogflowcx_v3/test_security_settings_service.py index 4a4330ca..0258d812 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_security_settings_service.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_security_settings_service.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -106,6 +113,7 @@ def test__get_default_mtls_endpoint(): [ (SecuritySettingsServiceClient, "grpc"), (SecuritySettingsServiceAsyncClient, "grpc_asyncio"), + (SecuritySettingsServiceClient, "rest"), ], ) def test_security_settings_service_client_from_service_account_info( @@ -121,7 +129,11 @@ def test_security_settings_service_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -129,6 +141,7 @@ def test_security_settings_service_client_from_service_account_info( [ (transports.SecuritySettingsServiceGrpcTransport, "grpc"), (transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SecuritySettingsServiceRestTransport, "rest"), ], ) def test_security_settings_service_client_service_account_always_use_jwt( @@ -154,6 +167,7 @@ def test_security_settings_service_client_service_account_always_use_jwt( [ (SecuritySettingsServiceClient, "grpc"), (SecuritySettingsServiceAsyncClient, "grpc_asyncio"), + (SecuritySettingsServiceClient, "rest"), ], ) def test_security_settings_service_client_from_service_account_file( @@ -176,13 +190,18 @@ def test_security_settings_service_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_security_settings_service_client_get_transport_class(): transport = SecuritySettingsServiceClient.get_transport_class() available_transports = [ transports.SecuritySettingsServiceGrpcTransport, + transports.SecuritySettingsServiceRestTransport, ] assert transport in available_transports @@ -203,6 +222,11 @@ def test_security_settings_service_client_get_transport_class(): transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + ), ], ) @mock.patch.object( @@ -358,6 +382,18 @@ def test_security_settings_service_client_client_options( "grpc_asyncio", "false", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + "true", + ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -563,6 +599,11 @@ def test_security_settings_service_client_get_mtls_endpoint_and_cert_source( transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + ), ], ) def test_security_settings_service_client_client_options_scopes( @@ -603,6 +644,12 @@ def test_security_settings_service_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + None, + ), ], ) def test_security_settings_service_client_client_options_credentials_file( @@ -2308,134 +2355,1737 @@ async def test_delete_security_settings_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + gcdc_security_settings.CreateSecuritySettingsRequest, + dict, + ], +) +def test_create_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["security_settings"] = { + "name": "name_value", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + assert ( + response.redaction_scope + == gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE + ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] + + +def test_create_security_settings_rest_required_fields( + request_type=gcdc_security_settings.CreateSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options=options, - transport=transport, - ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "securitySettings", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "post_create_security_settings", + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_create_security_settings", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_security_settings.CreateSecuritySettingsRequest.pb( + gcdc_security_settings.CreateSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_security_settings.SecuritySettings.to_json( + gcdc_security_settings.SecuritySettings() ) + request = gcdc_security_settings.CreateSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_security_settings.SecuritySettings() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + client.create_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_security_settings_rest_bad_request( + transport: str = "rest", + request_type=gcdc_security_settings.CreateSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = SecuritySettingsServiceClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["security_settings"] = { + "name": "name_value", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_security_settings(request) + + +def test_create_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.SecuritySettingsServiceGrpcAsyncIOTransport( + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*}/securitySettings" + % client.transport._host, + args[1], + ) + + +def test_create_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_security_settings( + gcdc_security_settings.CreateSecuritySettingsRequest(), + parent="parent_value", + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.SecuritySettingsServiceGrpcTransport, - transports.SecuritySettingsServiceGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + +def test_create_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + security_settings.GetSecuritySettingsRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = SecuritySettingsServiceClient.get_transport_class(transport_name)( +def test_get_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = SecuritySettingsServiceClient( - credentials=ga_credentials.AnonymousCredentials(), + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE ) - assert isinstance( - client.transport, - transports.SecuritySettingsServiceGrpcTransport, + assert ( + response.redaction_scope + == security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] -def test_security_settings_service_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SecuritySettingsServiceTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +def test_get_security_settings_rest_required_fields( + request_type=security_settings.GetSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) + # verify fields with default values are dropped -def test_security_settings_service_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.security_settings_service.transports.SecuritySettingsServiceTransport.__init__" + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "post_get_security_settings" + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "pre_get_security_settings" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = security_settings.GetSecuritySettingsRequest.pb( + security_settings.GetSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = security_settings.SecuritySettings.to_json( + security_settings.SecuritySettings() + ) + + request = security_settings.GetSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = security_settings.SecuritySettings() + + client.get_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_security_settings_rest_bad_request( + transport: str = "rest", request_type=security_settings.GetSecuritySettingsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_security_settings(request) + + +def test_get_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_get_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_security_settings( + security_settings.GetSecuritySettingsRequest(), + name="name_value", + ) + + +def test_get_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_security_settings.UpdateSecuritySettingsRequest, + dict, + ], +) +def test_update_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + request_init["security_settings"] = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE + ) + assert ( + response.redaction_scope + == gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE + ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] + + +def test_update_security_settings_rest_required_fields( + request_type=gcdc_security_settings.UpdateSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_security_settings._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "securitySettings", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "post_update_security_settings", + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_update_security_settings", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_security_settings.UpdateSecuritySettingsRequest.pb( + gcdc_security_settings.UpdateSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_security_settings.SecuritySettings.to_json( + gcdc_security_settings.SecuritySettings() + ) + + request = gcdc_security_settings.UpdateSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_security_settings.SecuritySettings() + + client.update_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_security_settings_rest_bad_request( + transport: str = "rest", + request_type=gcdc_security_settings.UpdateSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + request_init["security_settings"] = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_security_settings(request) + + +def test_update_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + + # get truthy value for each flattened field + mock_args = dict( + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{security_settings.name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_update_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_security_settings( + gcdc_security_settings.UpdateSecuritySettingsRequest(), + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + security_settings.ListSecuritySettingsRequest, + dict, + ], +) +def test_list_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSecuritySettingsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_security_settings_rest_required_fields( + request_type=security_settings.ListSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_security_settings._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "post_list_security_settings" + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "pre_list_security_settings" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = security_settings.ListSecuritySettingsRequest.pb( + security_settings.ListSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + security_settings.ListSecuritySettingsResponse.to_json( + security_settings.ListSecuritySettingsResponse() + ) + ) + + request = security_settings.ListSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = security_settings.ListSecuritySettingsResponse() + + client.list_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_security_settings_rest_bad_request( + transport: str = "rest", request_type=security_settings.ListSecuritySettingsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_security_settings(request) + + +def test_list_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*}/securitySettings" + % client.transport._host, + args[1], + ) + + +def test_list_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_security_settings( + security_settings.ListSecuritySettingsRequest(), + parent="parent_value", + ) + + +def test_list_security_settings_rest_pager(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + ], + next_page_token="abc", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[], + next_page_token="def", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + ], + next_page_token="ghi", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + security_settings.ListSecuritySettingsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2"} + + pager = client.list_security_settings(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, security_settings.SecuritySettings) for i in results) + + pages = list(client.list_security_settings(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + security_settings.DeleteSecuritySettingsRequest, + dict, + ], +) +def test_delete_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_security_settings(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_security_settings_rest_required_fields( + request_type=security_settings.DeleteSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_delete_security_settings", + ) as pre: + pre.assert_not_called() + pb_message = security_settings.DeleteSecuritySettingsRequest.pb( + security_settings.DeleteSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = security_settings.DeleteSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_security_settings_rest_bad_request( + transport: str = "rest", + request_type=security_settings.DeleteSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_security_settings(request) + + +def test_delete_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_security_settings( + security_settings.DeleteSecuritySettingsRequest(), + name="name_value", + ) + + +def test_delete_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SecuritySettingsServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SecuritySettingsServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SecuritySettingsServiceGrpcTransport, + transports.SecuritySettingsServiceGrpcAsyncIOTransport, + transports.SecuritySettingsServiceRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SecuritySettingsServiceClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SecuritySettingsServiceGrpcTransport, + ) + + +def test_security_settings_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SecuritySettingsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_security_settings_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.security_settings_service.transports.SecuritySettingsServiceTransport.__init__" ) as Transport: Transport.return_value = None transport = transports.SecuritySettingsServiceTransport( @@ -2550,6 +4200,7 @@ def test_security_settings_service_transport_auth_adc(transport_class): [ transports.SecuritySettingsServiceGrpcTransport, transports.SecuritySettingsServiceGrpcAsyncIOTransport, + transports.SecuritySettingsServiceRestTransport, ], ) def test_security_settings_service_transport_auth_gdch_credentials(transport_class): @@ -2654,11 +4305,23 @@ def test_security_settings_service_grpc_transport_client_cert_source_for_mtls( ) +def test_security_settings_service_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SecuritySettingsServiceRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_security_settings_service_host_no_port(transport_name): @@ -2669,7 +4332,11 @@ def test_security_settings_service_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2677,6 +4344,7 @@ def test_security_settings_service_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_security_settings_service_host_with_port(transport_name): @@ -2687,7 +4355,45 @@ def test_security_settings_service_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_security_settings_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SecuritySettingsServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SecuritySettingsServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_security_settings._session + session2 = client2.transport.create_security_settings._session + assert session1 != session2 + session1 = client1.transport.get_security_settings._session + session2 = client2.transport.get_security_settings._session + assert session1 != session2 + session1 = client1.transport.update_security_settings._session + session2 = client2.transport.update_security_settings._session + assert session1 != session2 + session1 = client1.transport.list_security_settings._session + session2 = client2.transport.list_security_settings._session + assert session1 != session2 + session1 = client1.transport.delete_security_settings._session + session2 = client2.transport.delete_security_settings._session + assert session1 != session2 def test_security_settings_service_grpc_transport_channel(): @@ -3040,6 +4746,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3761,6 +5753,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3778,6 +5771,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_session_entity_types.py b/tests/unit/gapic/dialogflowcx_v3/test_session_entity_types.py index 1fa4d141..e91a1e54 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_session_entity_types.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_session_entity_types.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -107,6 +114,7 @@ def test__get_default_mtls_endpoint(): [ (SessionEntityTypesClient, "grpc"), (SessionEntityTypesAsyncClient, "grpc_asyncio"), + (SessionEntityTypesClient, "rest"), ], ) def test_session_entity_types_client_from_service_account_info( @@ -122,7 +130,11 @@ def test_session_entity_types_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -130,6 +142,7 @@ def test_session_entity_types_client_from_service_account_info( [ (transports.SessionEntityTypesGrpcTransport, "grpc"), (transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SessionEntityTypesRestTransport, "rest"), ], ) def test_session_entity_types_client_service_account_always_use_jwt( @@ -155,6 +168,7 @@ def test_session_entity_types_client_service_account_always_use_jwt( [ (SessionEntityTypesClient, "grpc"), (SessionEntityTypesAsyncClient, "grpc_asyncio"), + (SessionEntityTypesClient, "rest"), ], ) def test_session_entity_types_client_from_service_account_file( @@ -177,13 +191,18 @@ def test_session_entity_types_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_session_entity_types_client_get_transport_class(): transport = SessionEntityTypesClient.get_transport_class() available_transports = [ transports.SessionEntityTypesGrpcTransport, + transports.SessionEntityTypesRestTransport, ] assert transport in available_transports @@ -200,6 +219,7 @@ def test_session_entity_types_client_get_transport_class(): transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (SessionEntityTypesClient, transports.SessionEntityTypesRestTransport, "rest"), ], ) @mock.patch.object( @@ -355,6 +375,18 @@ def test_session_entity_types_client_client_options( "grpc_asyncio", "false", ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + "true", + ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -554,6 +586,7 @@ def test_session_entity_types_client_get_mtls_endpoint_and_cert_source(client_cl transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (SessionEntityTypesClient, transports.SessionEntityTypesRestTransport, "rest"), ], ) def test_session_entity_types_client_client_options_scopes( @@ -594,6 +627,12 @@ def test_session_entity_types_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + None, + ), ], ) def test_session_entity_types_client_client_options_credentials_file( @@ -2198,222 +2237,1741 @@ async def test_delete_session_entity_type_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.ListSessionEntityTypesRequest, + dict, + ], +) +def test_list_session_entity_types_rest(request_type): + client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_session_entity_types(request) + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSessionEntityTypesPager) + assert response.next_page_token == "next_page_token_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + +def test_list_session_entity_types_rest_required_fields( + request_type=session_entity_type.ListSessionEntityTypesRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - client = SessionEntityTypesClient(transport=transport) - assert client.transport is transport + # verify fields with default values are dropped -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_session_entity_types._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_session_entity_types._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) ) - channel = transport.grpc_channel - assert channel + jsonified_request.update(unset_fields) - transport = transports.SessionEntityTypesGrpcAsyncIOTransport( + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -@pytest.mark.parametrize( - "transport_class", - [ - transports.SessionEntityTypesGrpcTransport, - transports.SessionEntityTypesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = SessionEntityTypesClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name + response = client.list_session_entity_types(request) + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = SessionEntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), + +def test_list_session_entity_types_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.SessionEntityTypesGrpcTransport, + + unset_fields = transport.list_session_entity_types._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) ) -def test_session_entity_types_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SessionEntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_session_entity_types_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_list_session_entity_types" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_list_session_entity_types" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session_entity_type.ListSessionEntityTypesRequest.pb( + session_entity_type.ListSessionEntityTypesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + session_entity_type.ListSessionEntityTypesResponse.to_json( + session_entity_type.ListSessionEntityTypesResponse() + ) ) + request = session_entity_type.ListSessionEntityTypesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session_entity_type.ListSessionEntityTypesResponse() -def test_session_entity_types_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.SessionEntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_session_entity_types( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_session_entity_types", - "get_session_entity_type", - "create_session_entity_type", - "update_session_entity_type", - "delete_session_entity_type", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + pre.assert_called_once() + post.assert_called_once() + + +def test_list_session_entity_types_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.ListSessionEntityTypesRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_session_entity_types(request) -def test_session_entity_types_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.SessionEntityTypesTransport( - credentials_file="credentials.json", - quota_project_id="octopus", +def test_list_session_entity_types_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + client.list_session_entity_types(**mock_args) -def test_session_entity_types_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.SessionEntityTypesTransport() - adc.assert_called_once() + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes" + % client.transport._host, + args[1], + ) -def test_session_entity_types_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - SessionEntityTypesClient() - adc.assert_called_once_with( - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id=None, +def test_list_session_entity_types_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_session_entity_types( + session_entity_type.ListSessionEntityTypesRequest(), + parent="parent_value", ) -@pytest.mark.parametrize( - "transport_class", +def test_list_session_entity_types_rest_pager(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + ], + next_page_token="abc", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[], + next_page_token="def", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + ], + next_page_token="ghi", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + session_entity_type.ListSessionEntityTypesResponse.to_json(x) + for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + pager = client.list_session_entity_types(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, session_entity_type.SessionEntityType) for i in results + ) + + pages = list(client.list_session_entity_types(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.GetSessionEntityTypeRequest, + dict, + ], +) +def test_get_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_get_session_entity_type_rest_required_fields( + request_type=session_entity_type.GetSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_get_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_get_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session_entity_type.GetSessionEntityTypeRequest.pb( + session_entity_type.GetSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session_entity_type.SessionEntityType.to_json( + session_entity_type.SessionEntityType() + ) + + request = session_entity_type.GetSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session_entity_type.SessionEntityType() + + client.get_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.GetSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_session_entity_type(request) + + +def test_get_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_get_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_session_entity_type( + session_entity_type.GetSessionEntityTypeRequest(), + name="name_value", + ) + + +def test_get_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_session_entity_type.CreateSessionEntityTypeRequest, + dict, + ], +) +def test_create_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request_init["session_entity_type"] = { + "name": "name_value", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_create_session_entity_type_rest_required_fields( + request_type=gcdc_session_entity_type.CreateSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "sessionEntityType", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_create_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_create_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_session_entity_type.CreateSessionEntityTypeRequest.pb( + gcdc_session_entity_type.CreateSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_session_entity_type.SessionEntityType.to_json( + gcdc_session_entity_type.SessionEntityType() + ) + + request = gcdc_session_entity_type.CreateSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_session_entity_type.SessionEntityType() + + client.create_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=gcdc_session_entity_type.CreateSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request_init["session_entity_type"] = { + "name": "name_value", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_session_entity_type(request) + + +def test_create_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes" + % client.transport._host, + args[1], + ) + + +def test_create_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_session_entity_type( + gcdc_session_entity_type.CreateSessionEntityTypeRequest(), + parent="parent_value", + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + ) + + +def test_create_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + dict, + ], +) +def test_update_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + request_init["session_entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_update_session_entity_type_rest_required_fields( + request_type=gcdc_session_entity_type.UpdateSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_session_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("sessionEntityType",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_update_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_update_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_session_entity_type.UpdateSessionEntityTypeRequest.pb( + gcdc_session_entity_type.UpdateSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_session_entity_type.SessionEntityType.to_json( + gcdc_session_entity_type.SessionEntityType() + ) + + request = gcdc_session_entity_type.UpdateSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_session_entity_type.SessionEntityType() + + client.update_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=gcdc_session_entity_type.UpdateSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + request_init["session_entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_session_entity_type(request) + + +def test_update_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{session_entity_type.name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_session_entity_type( + gcdc_session_entity_type.UpdateSessionEntityTypeRequest(), + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.DeleteSessionEntityTypeRequest, + dict, + ], +) +def test_delete_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_session_entity_type_rest_required_fields( + request_type=session_entity_type.DeleteSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_delete_session_entity_type" + ) as pre: + pre.assert_not_called() + pb_message = session_entity_type.DeleteSessionEntityTypeRequest.pb( + session_entity_type.DeleteSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = session_entity_type.DeleteSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.DeleteSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_session_entity_type(request) + + +def test_delete_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_session_entity_type( + session_entity_type.DeleteSessionEntityTypeRequest(), + name="name_value", + ) + + +def test_delete_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SessionEntityTypesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SessionEntityTypesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SessionEntityTypesGrpcTransport, + transports.SessionEntityTypesGrpcAsyncIOTransport, + transports.SessionEntityTypesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SessionEntityTypesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SessionEntityTypesGrpcTransport, + ) + + +def test_session_entity_types_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SessionEntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_session_entity_types_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.SessionEntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_session_entity_types", + "get_session_entity_type", + "create_session_entity_type", + "update_session_entity_type", + "delete_session_entity_type", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_session_entity_types_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.SessionEntityTypesTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_session_entity_types_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.SessionEntityTypesTransport() + adc.assert_called_once() + + +def test_session_entity_types_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + SessionEntityTypesClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", [ transports.SessionEntityTypesGrpcTransport, transports.SessionEntityTypesGrpcAsyncIOTransport, @@ -2440,6 +3998,7 @@ def test_session_entity_types_transport_auth_adc(transport_class): [ transports.SessionEntityTypesGrpcTransport, transports.SessionEntityTypesGrpcAsyncIOTransport, + transports.SessionEntityTypesRestTransport, ], ) def test_session_entity_types_transport_auth_gdch_credentials(transport_class): @@ -2542,11 +4101,23 @@ def test_session_entity_types_grpc_transport_client_cert_source_for_mtls( ) +def test_session_entity_types_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SessionEntityTypesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_session_entity_types_host_no_port(transport_name): @@ -2557,7 +4128,11 @@ def test_session_entity_types_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2565,6 +4140,7 @@ def test_session_entity_types_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_session_entity_types_host_with_port(transport_name): @@ -2575,7 +4151,45 @@ def test_session_entity_types_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_session_entity_types_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SessionEntityTypesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SessionEntityTypesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_session_entity_types._session + session2 = client2.transport.list_session_entity_types._session + assert session1 != session2 + session1 = client1.transport.get_session_entity_type._session + session2 = client2.transport.get_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.create_session_entity_type._session + session2 = client2.transport.create_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.update_session_entity_type._session + session2 = client2.transport.update_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.delete_session_entity_type._session + session2 = client2.transport.delete_session_entity_type._session + assert session1 != session2 def test_session_entity_types_grpc_transport_channel(): @@ -2878,6 +4492,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3597,6 +5497,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3614,6 +5515,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_sessions.py b/tests/unit/gapic/dialogflowcx_v3/test_sessions.py index 65fb227d..5a5e305f 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_sessions.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_sessions.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -98,6 +105,7 @@ def test__get_default_mtls_endpoint(): [ (SessionsClient, "grpc"), (SessionsAsyncClient, "grpc_asyncio"), + (SessionsClient, "rest"), ], ) def test_sessions_client_from_service_account_info(client_class, transport_name): @@ -111,7 +119,11 @@ def test_sessions_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -119,6 +131,7 @@ def test_sessions_client_from_service_account_info(client_class, transport_name) [ (transports.SessionsGrpcTransport, "grpc"), (transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SessionsRestTransport, "rest"), ], ) def test_sessions_client_service_account_always_use_jwt( @@ -144,6 +157,7 @@ def test_sessions_client_service_account_always_use_jwt( [ (SessionsClient, "grpc"), (SessionsAsyncClient, "grpc_asyncio"), + (SessionsClient, "rest"), ], ) def test_sessions_client_from_service_account_file(client_class, transport_name): @@ -164,13 +178,18 @@ def test_sessions_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_sessions_client_get_transport_class(): transport = SessionsClient.get_transport_class() available_transports = [ transports.SessionsGrpcTransport, + transports.SessionsRestTransport, ] assert transport in available_transports @@ -183,6 +202,7 @@ def test_sessions_client_get_transport_class(): [ (SessionsClient, transports.SessionsGrpcTransport, "grpc"), (SessionsAsyncClient, transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (SessionsClient, transports.SessionsRestTransport, "rest"), ], ) @mock.patch.object( @@ -324,6 +344,8 @@ def test_sessions_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (SessionsClient, transports.SessionsRestTransport, "rest", "true"), + (SessionsClient, transports.SessionsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -513,6 +535,7 @@ def test_sessions_client_get_mtls_endpoint_and_cert_source(client_class): [ (SessionsClient, transports.SessionsGrpcTransport, "grpc"), (SessionsAsyncClient, transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (SessionsClient, transports.SessionsRestTransport, "rest"), ], ) def test_sessions_client_client_options_scopes( @@ -548,6 +571,7 @@ def test_sessions_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (SessionsClient, transports.SessionsRestTransport, "rest", None), ], ) def test_sessions_client_client_options_credentials_file( @@ -1194,6 +1218,605 @@ async def test_fulfill_intent_field_headers_async(): ) in kw["metadata"] +@pytest.mark.parametrize( + "request_type", + [ + session.DetectIntentRequest, + dict, + ], +) +def test_detect_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.DetectIntentResponse( + response_id="response_id_value", + output_audio=b"output_audio_blob", + response_type=session.DetectIntentResponse.ResponseType.PARTIAL, + allow_cancellation=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.DetectIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.detect_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.DetectIntentResponse) + assert response.response_id == "response_id_value" + assert response.output_audio == b"output_audio_blob" + assert response.response_type == session.DetectIntentResponse.ResponseType.PARTIAL + assert response.allow_cancellation is True + + +def test_detect_intent_rest_required_fields(request_type=session.DetectIntentRequest): + transport_class = transports.SessionsRestTransport + + request_init = {} + request_init["session"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detect_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["session"] = "session_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detect_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "session" in jsonified_request + assert jsonified_request["session"] == "session_value" + + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session.DetectIntentResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session.DetectIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.detect_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_detect_intent_rest_unset_required_fields(): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.detect_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "session", + "queryInput", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_detect_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_detect_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_detect_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.DetectIntentRequest.pb(session.DetectIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.DetectIntentResponse.to_json( + session.DetectIntentResponse() + ) + + request = session.DetectIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.DetectIntentResponse() + + client.detect_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_detect_intent_rest_bad_request( + transport: str = "rest", request_type=session.DetectIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.detect_intent(request) + + +def test_detect_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_detect_intent_rest_no_http_options(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = session.StreamingDetectIntentRequest() + requests = [request] + with pytest.raises(RuntimeError): + client.streaming_detect_intent(requests) + + +@pytest.mark.parametrize( + "request_type", + [ + session.MatchIntentRequest, + dict, + ], +) +def test_match_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.MatchIntentResponse( + text="text_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.MatchIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.match_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.MatchIntentResponse) + + +def test_match_intent_rest_required_fields(request_type=session.MatchIntentRequest): + transport_class = transports.SessionsRestTransport + + request_init = {} + request_init["session"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).match_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["session"] = "session_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).match_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "session" in jsonified_request + assert jsonified_request["session"] == "session_value" + + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session.MatchIntentResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session.MatchIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.match_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_match_intent_rest_unset_required_fields(): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.match_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "session", + "queryInput", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_match_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_match_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_match_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.MatchIntentRequest.pb(session.MatchIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.MatchIntentResponse.to_json( + session.MatchIntentResponse() + ) + + request = session.MatchIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.MatchIntentResponse() + + client.match_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_match_intent_rest_bad_request( + transport: str = "rest", request_type=session.MatchIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.match_intent(request) + + +def test_match_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + session.FulfillIntentRequest, + dict, + ], +) +def test_fulfill_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "match_intent_request": { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.FulfillIntentResponse( + response_id="response_id_value", + output_audio=b"output_audio_blob", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.FulfillIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.fulfill_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.FulfillIntentResponse) + assert response.response_id == "response_id_value" + assert response.output_audio == b"output_audio_blob" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_fulfill_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_fulfill_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_fulfill_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.FulfillIntentRequest.pb(session.FulfillIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.FulfillIntentResponse.to_json( + session.FulfillIntentResponse() + ) + + request = session.FulfillIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.FulfillIntentResponse() + + client.fulfill_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_fulfill_intent_rest_bad_request( + transport: str = "rest", request_type=session.FulfillIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "match_intent_request": { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.fulfill_intent(request) + + +def test_fulfill_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_detect_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # Since a `google.api.http` annotation is required for using a rest transport + # method, this should error. + with pytest.raises(NotImplementedError) as not_implemented_error: + client.streaming_detect_intent({}) + assert "Method StreamingDetectIntent is not available over REST transport" in str( + not_implemented_error.value + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.SessionsGrpcTransport( @@ -1275,6 +1898,7 @@ def test_transport_get_channel(): [ transports.SessionsGrpcTransport, transports.SessionsGrpcAsyncIOTransport, + transports.SessionsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1289,6 +1913,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1435,6 +2060,7 @@ def test_sessions_transport_auth_adc(transport_class): [ transports.SessionsGrpcTransport, transports.SessionsGrpcAsyncIOTransport, + transports.SessionsRestTransport, ], ) def test_sessions_transport_auth_gdch_credentials(transport_class): @@ -1532,11 +2158,23 @@ def test_sessions_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_sessions_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SessionsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_sessions_host_no_port(transport_name): @@ -1547,7 +2185,11 @@ def test_sessions_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1555,6 +2197,7 @@ def test_sessions_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_sessions_host_with_port(transport_name): @@ -1565,7 +2208,42 @@ def test_sessions_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_sessions_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SessionsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SessionsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.detect_intent._session + session2 = client2.transport.detect_intent._session + assert session1 != session2 + session1 = client1.transport.streaming_detect_intent._session + session2 = client2.transport.streaming_detect_intent._session + assert session1 != session2 + session1 = client1.transport.match_intent._session + session2 = client2.transport.match_intent._session + assert session1 != session2 + session1 = client1.transport.fulfill_intent._session + session2 = client2.transport.fulfill_intent._session + assert session1 != session2 def test_sessions_grpc_transport_channel(): @@ -2105,6 +2783,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SessionsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2822,6 +3786,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2839,6 +3804,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_test_cases.py b/tests/unit/gapic/dialogflowcx_v3/test_test_cases.py index 65f52552..a63c96c2 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_test_cases.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_test_cases.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -108,6 +115,7 @@ def test__get_default_mtls_endpoint(): [ (TestCasesClient, "grpc"), (TestCasesAsyncClient, "grpc_asyncio"), + (TestCasesClient, "rest"), ], ) def test_test_cases_client_from_service_account_info(client_class, transport_name): @@ -121,7 +129,11 @@ def test_test_cases_client_from_service_account_info(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -129,6 +141,7 @@ def test_test_cases_client_from_service_account_info(client_class, transport_nam [ (transports.TestCasesGrpcTransport, "grpc"), (transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.TestCasesRestTransport, "rest"), ], ) def test_test_cases_client_service_account_always_use_jwt( @@ -154,6 +167,7 @@ def test_test_cases_client_service_account_always_use_jwt( [ (TestCasesClient, "grpc"), (TestCasesAsyncClient, "grpc_asyncio"), + (TestCasesClient, "rest"), ], ) def test_test_cases_client_from_service_account_file(client_class, transport_name): @@ -174,13 +188,18 @@ def test_test_cases_client_from_service_account_file(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_test_cases_client_get_transport_class(): transport = TestCasesClient.get_transport_class() available_transports = [ transports.TestCasesGrpcTransport, + transports.TestCasesRestTransport, ] assert transport in available_transports @@ -197,6 +216,7 @@ def test_test_cases_client_get_transport_class(): transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest"), ], ) @mock.patch.object( @@ -340,6 +360,8 @@ def test_test_cases_client_client_options( "grpc_asyncio", "false", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest", "true"), + (TestCasesClient, transports.TestCasesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -533,6 +555,7 @@ def test_test_cases_client_get_mtls_endpoint_and_cert_source(client_class): transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest"), ], ) def test_test_cases_client_client_options_scopes( @@ -568,6 +591,7 @@ def test_test_cases_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (TestCasesClient, transports.TestCasesRestTransport, "rest", None), ], ) def test_test_cases_client_client_options_credentials_file( @@ -3543,149 +3567,4113 @@ async def test_get_test_case_result_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TestCasesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + test_case.ListTestCasesRequest, + dict, + ], +) +def test_list_test_cases_rest(request_type): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = TestCasesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.TestCasesGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_test_cases(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTestCasesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_test_cases_rest_required_fields( + request_type=test_case.ListTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_cases._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + "view", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - with pytest.raises(ValueError): - client = TestCasesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + + unset_fields = transport.list_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + "view", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.TestCasesGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TestCasesClient( - client_options=options, - transport=transport, + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_list_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_list_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ListTestCasesRequest.pb(test_case.ListTestCasesRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.ListTestCasesResponse.to_json( + test_case.ListTestCasesResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TestCasesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = test_case.ListTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.ListTestCasesResponse() + + client.list_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.TestCasesGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ListTestCasesRequest +): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = TestCasesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TestCasesGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_test_cases(request) + + +def test_list_test_cases_rest_flattened(): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = TestCasesClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.TestCasesGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_test_cases(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/testCases" + % client.transport._host, + args[1], + ) + + +def test_list_test_cases_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.TestCasesGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_test_cases( + test_case.ListTestCasesRequest(), + parent="parent_value", + ) + + +def test_list_test_cases_rest_pager(transport: str = "rest"): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + test_case.TestCase(), + test_case.TestCase(), + ], + next_page_token="abc", + ), + test_case.ListTestCasesResponse( + test_cases=[], + next_page_token="def", + ), + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + ], + next_page_token="ghi", + ), + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + test_case.TestCase(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(test_case.ListTestCasesResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.TestCasesGrpcTransport, - transports.TestCasesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_test_cases(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, test_case.TestCase) for i in results) + + pages = list(client.list_test_cases(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + test_case.BatchDeleteTestCasesRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = TestCasesClient.get_transport_class(transport_name)( +def test_batch_delete_test_cases_rest(request_type): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.batch_delete_test_cases(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_batch_delete_test_cases_rest_required_fields( + request_type=test_case.BatchDeleteTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request_init["names"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_delete_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + jsonified_request["names"] = "names_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_delete_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + assert "names" in jsonified_request + assert jsonified_request["names"] == "names_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.batch_delete_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_batch_delete_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.TestCasesGrpcTransport, + + unset_fields = transport.batch_delete_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "names", + ) + ) ) -def test_test_cases_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.TestCasesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_batch_delete_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_batch_delete_test_cases" + ) as pre: + pre.assert_not_called() + pb_message = test_case.BatchDeleteTestCasesRequest.pb( + test_case.BatchDeleteTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = test_case.BatchDeleteTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.batch_delete_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() -def test_test_cases_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.test_cases.transports.TestCasesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.TestCasesTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_test_cases", - "batch_delete_test_cases", - "get_test_case", - "create_test_case", - "update_test_case", - "run_test_case", +def test_batch_delete_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.BatchDeleteTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.batch_delete_test_cases(request) + + +def test_batch_delete_test_cases_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.batch_delete_test_cases(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/testCases:batchDelete" + % client.transport._host, + args[1], + ) + + +def test_batch_delete_test_cases_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.batch_delete_test_cases( + test_case.BatchDeleteTestCasesRequest(), + parent="parent_value", + ) + + +def test_batch_delete_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.GetTestCaseRequest, + dict, + ], +) +def test_get_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_get_test_case_rest_required_fields(request_type=test_case.GetTestCaseRequest): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_test_case._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_get_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_get_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.GetTestCaseRequest.pb(test_case.GetTestCaseRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.TestCase.to_json(test_case.TestCase()) + + request = test_case.GetTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.TestCase() + + client.get_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_test_case_rest_bad_request( + transport: str = "rest", request_type=test_case.GetTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_test_case(request) + + +def test_get_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/testCases/*}" + % client.transport._host, + args[1], + ) + + +def test_get_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_test_case( + test_case.GetTestCaseRequest(), + name="name_value", + ) + + +def test_get_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_test_case.CreateTestCaseRequest, + dict, + ], +) +def test_create_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["test_case"] = { + "name": "name_value", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_create_test_case_rest_required_fields( + request_type=gcdc_test_case.CreateTestCaseRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_test_case._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "testCase", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_create_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_create_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_test_case.CreateTestCaseRequest.pb( + gcdc_test_case.CreateTestCaseRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_test_case.TestCase.to_json( + gcdc_test_case.TestCase() + ) + + request = gcdc_test_case.CreateTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_test_case.TestCase() + + client.create_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_test_case_rest_bad_request( + transport: str = "rest", request_type=gcdc_test_case.CreateTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["test_case"] = { + "name": "name_value", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_test_case(request) + + +def test_create_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + test_case=gcdc_test_case.TestCase(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/testCases" + % client.transport._host, + args[1], + ) + + +def test_create_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_test_case( + gcdc_test_case.CreateTestCaseRequest(), + parent="parent_value", + test_case=gcdc_test_case.TestCase(name="name_value"), + ) + + +def test_create_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_test_case.UpdateTestCaseRequest, + dict, + ], +) +def test_update_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + request_init["test_case"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_update_test_case_rest_required_fields( + request_type=gcdc_test_case.UpdateTestCaseRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_test_case._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_test_case._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "testCase", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_update_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_update_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_test_case.UpdateTestCaseRequest.pb( + gcdc_test_case.UpdateTestCaseRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_test_case.TestCase.to_json( + gcdc_test_case.TestCase() + ) + + request = gcdc_test_case.UpdateTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_test_case.TestCase() + + client.update_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_test_case_rest_bad_request( + transport: str = "rest", request_type=gcdc_test_case.UpdateTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + request_init["test_case"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_test_case(request) + + +def test_update_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + test_case=gcdc_test_case.TestCase(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{test_case.name=projects/*/locations/*/agents/*/testCases/*}" + % client.transport._host, + args[1], + ) + + +def test_update_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_test_case( + gcdc_test_case.UpdateTestCaseRequest(), + test_case=gcdc_test_case.TestCase(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.RunTestCaseRequest, + dict, + ], +) +def test_run_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.run_test_case(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_run_test_case_rest_required_fields(request_type=test_case.RunTestCaseRequest): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.run_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_run_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.run_test_case._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_run_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_run_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_run_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.RunTestCaseRequest.pb(test_case.RunTestCaseRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.RunTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.run_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_run_test_case_rest_bad_request( + transport: str = "rest", request_type=test_case.RunTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.run_test_case(request) + + +def test_run_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.BatchRunTestCasesRequest, + dict, + ], +) +def test_batch_run_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.batch_run_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_batch_run_test_cases_rest_required_fields( + request_type=test_case.BatchRunTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request_init["test_cases"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_run_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + jsonified_request["testCases"] = "test_cases_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_run_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + assert "testCases" in jsonified_request + assert jsonified_request["testCases"] == "test_cases_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.batch_run_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_batch_run_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.batch_run_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "testCases", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_batch_run_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_batch_run_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_batch_run_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.BatchRunTestCasesRequest.pb( + test_case.BatchRunTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.BatchRunTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.batch_run_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_batch_run_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.BatchRunTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.batch_run_test_cases(request) + + +def test_batch_run_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.CalculateCoverageRequest, + dict, + ], +) +def test_calculate_coverage_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"agent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.CalculateCoverageResponse( + agent="agent_value", + intent_coverage=test_case.IntentCoverage( + intents=[test_case.IntentCoverage.Intent(intent="intent_value")] + ), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.CalculateCoverageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.calculate_coverage(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.CalculateCoverageResponse) + assert response.agent == "agent_value" + + +def test_calculate_coverage_rest_required_fields( + request_type=test_case.CalculateCoverageRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["agent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).calculate_coverage._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["agent"] = "agent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).calculate_coverage._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("type_",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "agent" in jsonified_request + assert jsonified_request["agent"] == "agent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.CalculateCoverageResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.CalculateCoverageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.calculate_coverage(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_calculate_coverage_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.calculate_coverage._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("type",)) + & set( + ( + "agent", + "type", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_calculate_coverage_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_calculate_coverage" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_calculate_coverage" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.CalculateCoverageRequest.pb( + test_case.CalculateCoverageRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.CalculateCoverageResponse.to_json( + test_case.CalculateCoverageResponse() + ) + + request = test_case.CalculateCoverageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.CalculateCoverageResponse() + + client.calculate_coverage( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_calculate_coverage_rest_bad_request( + transport: str = "rest", request_type=test_case.CalculateCoverageRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"agent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.calculate_coverage(request) + + +def test_calculate_coverage_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ImportTestCasesRequest, + dict, + ], +) +def test_import_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.import_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_import_test_cases_rest_required_fields( + request_type=test_case.ImportTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.import_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_import_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.import_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_import_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_import_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_import_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ImportTestCasesRequest.pb( + test_case.ImportTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.ImportTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.import_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_import_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ImportTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.import_test_cases(request) + + +def test_import_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ExportTestCasesRequest, + dict, + ], +) +def test_export_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_test_cases_rest_required_fields( + request_type=test_case.ExportTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_export_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_export_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ExportTestCasesRequest.pb( + test_case.ExportTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.ExportTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ExportTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_test_cases(request) + + +def test_export_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ListTestCaseResultsRequest, + dict, + ], +) +def test_list_test_case_results_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_test_case_results(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTestCaseResultsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_test_case_results_rest_required_fields( + request_type=test_case.ListTestCaseResultsRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_case_results._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_case_results._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "filter", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_test_case_results(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_test_case_results_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_test_case_results._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "filter", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_test_case_results_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_list_test_case_results" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_list_test_case_results" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ListTestCaseResultsRequest.pb( + test_case.ListTestCaseResultsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.ListTestCaseResultsResponse.to_json( + test_case.ListTestCaseResultsResponse() + ) + + request = test_case.ListTestCaseResultsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.ListTestCaseResultsResponse() + + client.list_test_case_results( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_test_case_results_rest_bad_request( + transport: str = "rest", request_type=test_case.ListTestCaseResultsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_test_case_results(request) + + +def test_list_test_case_results_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_test_case_results(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/testCases/*}/results" + % client.transport._host, + args[1], + ) + + +def test_list_test_case_results_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_test_case_results( + test_case.ListTestCaseResultsRequest(), + parent="parent_value", + ) + + +def test_list_test_case_results_rest_pager(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + test_case.TestCaseResult(), + test_case.TestCaseResult(), + ], + next_page_token="abc", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[], + next_page_token="def", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + ], + next_page_token="ghi", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + test_case.TestCaseResult(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + test_case.ListTestCaseResultsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + pager = client.list_test_case_results(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, test_case.TestCaseResult) for i in results) + + pages = list(client.list_test_case_results(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.GetTestCaseResultRequest, + dict, + ], +) +def test_get_test_case_result_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult( + name="name_value", + environment="environment_value", + test_result=test_case.TestResult.PASSED, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_test_case_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.TestCaseResult) + assert response.name == "name_value" + assert response.environment == "environment_value" + assert response.test_result == test_case.TestResult.PASSED + + +def test_get_test_case_result_rest_required_fields( + request_type=test_case.GetTestCaseResultRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_test_case_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_test_case_result_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_test_case_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_test_case_result_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_get_test_case_result" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_get_test_case_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.GetTestCaseResultRequest.pb( + test_case.GetTestCaseResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.TestCaseResult.to_json( + test_case.TestCaseResult() + ) + + request = test_case.GetTestCaseResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.TestCaseResult() + + client.get_test_case_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_test_case_result_rest_bad_request( + transport: str = "rest", request_type=test_case.GetTestCaseResultRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_test_case_result(request) + + +def test_get_test_case_result_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_test_case_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/testCases/*/results/*}" + % client.transport._host, + args[1], + ) + + +def test_get_test_case_result_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_test_case_result( + test_case.GetTestCaseResultRequest(), + name="name_value", + ) + + +def test_get_test_case_result_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TestCasesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TestCasesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = TestCasesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TestCasesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TestCasesGrpcTransport, + transports.TestCasesGrpcAsyncIOTransport, + transports.TestCasesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = TestCasesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.TestCasesGrpcTransport, + ) + + +def test_test_cases_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.TestCasesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_test_cases_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.test_cases.transports.TestCasesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TestCasesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_test_cases", + "batch_delete_test_cases", + "get_test_case", + "create_test_case", + "update_test_case", + "run_test_case", "batch_run_test_cases", "calculate_coverage", "import_test_cases", @@ -3797,6 +7785,7 @@ def test_test_cases_transport_auth_adc(transport_class): [ transports.TestCasesGrpcTransport, transports.TestCasesGrpcAsyncIOTransport, + transports.TestCasesRestTransport, ], ) def test_test_cases_transport_auth_gdch_credentials(transport_class): @@ -3894,11 +7883,40 @@ def test_test_cases_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_test_cases_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.TestCasesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_test_cases_rest_lro_client(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_test_cases_host_no_port(transport_name): @@ -3909,7 +7927,11 @@ def test_test_cases_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3917,6 +7939,7 @@ def test_test_cases_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_test_cases_host_with_port(transport_name): @@ -3927,7 +7950,66 @@ def test_test_cases_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_test_cases_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = TestCasesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = TestCasesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_test_cases._session + session2 = client2.transport.list_test_cases._session + assert session1 != session2 + session1 = client1.transport.batch_delete_test_cases._session + session2 = client2.transport.batch_delete_test_cases._session + assert session1 != session2 + session1 = client1.transport.get_test_case._session + session2 = client2.transport.get_test_case._session + assert session1 != session2 + session1 = client1.transport.create_test_case._session + session2 = client2.transport.create_test_case._session + assert session1 != session2 + session1 = client1.transport.update_test_case._session + session2 = client2.transport.update_test_case._session + assert session1 != session2 + session1 = client1.transport.run_test_case._session + session2 = client2.transport.run_test_case._session + assert session1 != session2 + session1 = client1.transport.batch_run_test_cases._session + session2 = client2.transport.batch_run_test_cases._session + assert session1 != session2 + session1 = client1.transport.calculate_coverage._session + session2 = client2.transport.calculate_coverage._session + assert session1 != session2 + session1 = client1.transport.import_test_cases._session + session2 = client2.transport.import_test_cases._session + assert session1 != session2 + session1 = client1.transport.export_test_cases._session + session2 = client2.transport.export_test_cases._session + assert session1 != session2 + session1 = client1.transport.list_test_case_results._session + session2 = client2.transport.list_test_case_results._session + assert session1 != session2 + session1 = client1.transport.get_test_case_result._session + session2 = client2.transport.get_test_case_result._session + assert session1 != session2 def test_test_cases_grpc_transport_channel(): @@ -4524,6 +8606,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -5241,6 +9609,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -5258,6 +9627,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_transition_route_groups.py b/tests/unit/gapic/dialogflowcx_v3/test_transition_route_groups.py index b911828a..47473728 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_transition_route_groups.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_transition_route_groups.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -110,6 +117,7 @@ def test__get_default_mtls_endpoint(): [ (TransitionRouteGroupsClient, "grpc"), (TransitionRouteGroupsAsyncClient, "grpc_asyncio"), + (TransitionRouteGroupsClient, "rest"), ], ) def test_transition_route_groups_client_from_service_account_info( @@ -125,7 +133,11 @@ def test_transition_route_groups_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -133,6 +145,7 @@ def test_transition_route_groups_client_from_service_account_info( [ (transports.TransitionRouteGroupsGrpcTransport, "grpc"), (transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.TransitionRouteGroupsRestTransport, "rest"), ], ) def test_transition_route_groups_client_service_account_always_use_jwt( @@ -158,6 +171,7 @@ def test_transition_route_groups_client_service_account_always_use_jwt( [ (TransitionRouteGroupsClient, "grpc"), (TransitionRouteGroupsAsyncClient, "grpc_asyncio"), + (TransitionRouteGroupsClient, "rest"), ], ) def test_transition_route_groups_client_from_service_account_file( @@ -180,13 +194,18 @@ def test_transition_route_groups_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_transition_route_groups_client_get_transport_class(): transport = TransitionRouteGroupsClient.get_transport_class() available_transports = [ transports.TransitionRouteGroupsGrpcTransport, + transports.TransitionRouteGroupsRestTransport, ] assert transport in available_transports @@ -207,6 +226,11 @@ def test_transition_route_groups_client_get_transport_class(): transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + ), ], ) @mock.patch.object( @@ -362,6 +386,18 @@ def test_transition_route_groups_client_client_options( "grpc_asyncio", "false", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + "true", + ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -565,6 +601,11 @@ def test_transition_route_groups_client_get_mtls_endpoint_and_cert_source(client transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + ), ], ) def test_transition_route_groups_client_client_options_scopes( @@ -605,6 +646,12 @@ def test_transition_route_groups_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + None, + ), ], ) def test_transition_route_groups_client_client_options_credentials_file( @@ -2204,208 +2251,2050 @@ async def test_delete_transition_route_group_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.ListTransitionRouteGroupsRequest, + dict, + ], +) +def test_list_transition_route_groups_rest(request_type): + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_transition_route_groups(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTransitionRouteGroupsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_transition_route_groups_rest_required_fields( + request_type=transition_route_group.ListTransitionRouteGroupsRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_transition_route_groups._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_transition_route_groups._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = TransitionRouteGroupsClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = ( + transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value + ) + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_transition_route_groups(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_transition_route_groups_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.TransitionRouteGroupsGrpcAsyncIOTransport( + unset_fields = transport.list_transition_route_groups._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_transition_route_groups_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_list_transition_route_groups", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_list_transition_route_groups", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = transition_route_group.ListTransitionRouteGroupsRequest.pb( + transition_route_group.ListTransitionRouteGroupsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + transition_route_group.ListTransitionRouteGroupsResponse.to_json( + transition_route_group.ListTransitionRouteGroupsResponse() + ) + ) + request = transition_route_group.ListTransitionRouteGroupsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = transition_route_group.ListTransitionRouteGroupsResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.TransitionRouteGroupsGrpcTransport, - transports.TransitionRouteGroupsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_transition_route_groups( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = TransitionRouteGroupsClient.get_transport_class(transport_name)( + +def test_list_transition_route_groups_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.ListTransitionRouteGroupsRequest, +): + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_transition_route_groups(request) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + +def test_list_transition_route_groups_rest_flattened(): client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.TransitionRouteGroupsGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse() -def test_transition_route_groups_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.TransitionRouteGroupsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) -def test_transition_route_groups_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.TransitionRouteGroupsTransport( - credentials=ga_credentials.AnonymousCredentials(), + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_transition_route_groups", - "get_transition_route_group", - "create_transition_route_group", - "update_transition_route_group", - "delete_transition_route_group", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", - ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) + client.list_transition_route_groups(**mock_args) - with pytest.raises(NotImplementedError): - transport.close() + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups" + % client.transport._host, + args[1], + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_transition_route_groups_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_transition_route_groups_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.TransitionRouteGroupsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_transition_route_groups( + transition_route_group.ListTransitionRouteGroupsRequest(), + parent="parent_value", ) -def test_transition_route_groups_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.TransitionRouteGroupsTransport() - adc.assert_called_once() - +def test_list_transition_route_groups_rest_pager(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_transition_route_groups_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + ], + next_page_token="abc", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[], + next_page_token="def", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + ], + next_page_token="ghi", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + transition_route_group.ListTransitionRouteGroupsResponse.to_json(x) + for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_transition_route_groups(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, transition_route_group.TransitionRouteGroup) for i in results + ) + + pages = list(client.list_transition_route_groups(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.GetTransitionRouteGroupRequest, + dict, + ], +) +def test_get_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.TransitionRouteGroup.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_get_transition_route_group_rest_required_fields( + request_type=transition_route_group.GetTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_transition_route_group._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_get_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_get_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = transition_route_group.GetTransitionRouteGroupRequest.pb( + transition_route_group.GetTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = transition_route_group.TransitionRouteGroup.to_json( + transition_route_group.TransitionRouteGroup() + ) + + request = transition_route_group.GetTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = transition_route_group.TransitionRouteGroup() + + client.get_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.GetTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_transition_route_group(request) + + +def test_get_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.TransitionRouteGroup.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_get_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_transition_route_group( + transition_route_group.GetTransitionRouteGroupRequest(), + name="name_value", + ) + + +def test_get_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + dict, + ], +) +def test_create_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["transition_route_group"] = { + "name": "name_value", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_create_transition_route_group_rest_required_fields( + request_type=gcdc_transition_route_group.CreateTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "transitionRouteGroup", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_create_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_create_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_transition_route_group.CreateTransitionRouteGroupRequest.pb( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + gcdc_transition_route_group.TransitionRouteGroup.to_json( + gcdc_transition_route_group.TransitionRouteGroup() + ) + ) + + request = gcdc_transition_route_group.CreateTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_transition_route_group.TransitionRouteGroup() + + client.create_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=gcdc_transition_route_group.CreateTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["transition_route_group"] = { + "name": "name_value", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_transition_route_group(request) + + +def test_create_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups" + % client.transport._host, + args[1], + ) + + +def test_create_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_transition_route_group( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest(), + parent="parent_value", + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + ) + + +def test_create_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + dict, + ], +) +def test_update_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + request_init["transition_route_group"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_update_transition_route_group_rest_required_fields( + request_type=gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("transitionRouteGroup",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_update_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_update_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_transition_route_group.UpdateTransitionRouteGroupRequest.pb( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + gcdc_transition_route_group.TransitionRouteGroup.to_json( + gcdc_transition_route_group.TransitionRouteGroup() + ) + ) + + request = gcdc_transition_route_group.UpdateTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_transition_route_group.TransitionRouteGroup() + + client.update_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + request_init["transition_route_group"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_transition_route_group(request) + + +def test_update_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{transition_route_group.name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_update_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_transition_route_group( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest(), + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.DeleteTransitionRouteGroupRequest, + dict, + ], +) +def test_delete_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_transition_route_group_rest_required_fields( + request_type=transition_route_group.DeleteTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_delete_transition_route_group", + ) as pre: + pre.assert_not_called() + pb_message = transition_route_group.DeleteTransitionRouteGroupRequest.pb( + transition_route_group.DeleteTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = transition_route_group.DeleteTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.DeleteTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_transition_route_group(request) + + +def test_delete_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_transition_route_group( + transition_route_group.DeleteTransitionRouteGroupRequest(), + name="name_value", + ) + + +def test_delete_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = TransitionRouteGroupsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TransitionRouteGroupsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TransitionRouteGroupsGrpcTransport, + transports.TransitionRouteGroupsGrpcAsyncIOTransport, + transports.TransitionRouteGroupsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = TransitionRouteGroupsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.TransitionRouteGroupsGrpcTransport, + ) + + +def test_transition_route_groups_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.TransitionRouteGroupsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_transition_route_groups_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TransitionRouteGroupsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_transition_route_groups", + "get_transition_route_group", + "create_transition_route_group", + "update_transition_route_group", + "delete_transition_route_group", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_transition_route_groups_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.TransitionRouteGroupsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_transition_route_groups_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.TransitionRouteGroupsTransport() + adc.assert_called_once() + + +def test_transition_route_groups_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: adc.return_value = (ga_credentials.AnonymousCredentials(), None) TransitionRouteGroupsClient() adc.assert_called_once_with( @@ -2446,6 +4335,7 @@ def test_transition_route_groups_transport_auth_adc(transport_class): [ transports.TransitionRouteGroupsGrpcTransport, transports.TransitionRouteGroupsGrpcAsyncIOTransport, + transports.TransitionRouteGroupsRestTransport, ], ) def test_transition_route_groups_transport_auth_gdch_credentials(transport_class): @@ -2550,11 +4440,23 @@ def test_transition_route_groups_grpc_transport_client_cert_source_for_mtls( ) +def test_transition_route_groups_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.TransitionRouteGroupsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_transition_route_groups_host_no_port(transport_name): @@ -2565,7 +4467,11 @@ def test_transition_route_groups_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2573,6 +4479,7 @@ def test_transition_route_groups_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_transition_route_groups_host_with_port(transport_name): @@ -2583,7 +4490,45 @@ def test_transition_route_groups_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_transition_route_groups_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = TransitionRouteGroupsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = TransitionRouteGroupsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_transition_route_groups._session + session2 = client2.transport.list_transition_route_groups._session + assert session1 != session2 + session1 = client1.transport.get_transition_route_group._session + session2 = client2.transport.get_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.create_transition_route_group._session + session2 = client2.transport.create_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.update_transition_route_group._session + session2 = client2.transport.update_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.delete_transition_route_group._session + session2 = client2.transport.delete_transition_route_group._session + assert session1 != session2 def test_transition_route_groups_grpc_transport_channel(): @@ -3007,6 +4952,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3728,6 +5959,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3745,6 +5977,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_versions.py b/tests/unit/gapic/dialogflowcx_v3/test_versions.py index 1859b272..452c887d 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_versions.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_versions.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -102,6 +109,7 @@ def test__get_default_mtls_endpoint(): [ (VersionsClient, "grpc"), (VersionsAsyncClient, "grpc_asyncio"), + (VersionsClient, "rest"), ], ) def test_versions_client_from_service_account_info(client_class, transport_name): @@ -115,7 +123,11 @@ def test_versions_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -123,6 +135,7 @@ def test_versions_client_from_service_account_info(client_class, transport_name) [ (transports.VersionsGrpcTransport, "grpc"), (transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.VersionsRestTransport, "rest"), ], ) def test_versions_client_service_account_always_use_jwt( @@ -148,6 +161,7 @@ def test_versions_client_service_account_always_use_jwt( [ (VersionsClient, "grpc"), (VersionsAsyncClient, "grpc_asyncio"), + (VersionsClient, "rest"), ], ) def test_versions_client_from_service_account_file(client_class, transport_name): @@ -168,13 +182,18 @@ def test_versions_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_versions_client_get_transport_class(): transport = VersionsClient.get_transport_class() available_transports = [ transports.VersionsGrpcTransport, + transports.VersionsRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_versions_client_get_transport_class(): [ (VersionsClient, transports.VersionsGrpcTransport, "grpc"), (VersionsAsyncClient, transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (VersionsClient, transports.VersionsRestTransport, "rest"), ], ) @mock.patch.object( @@ -328,6 +348,8 @@ def test_versions_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (VersionsClient, transports.VersionsRestTransport, "rest", "true"), + (VersionsClient, transports.VersionsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -517,6 +539,7 @@ def test_versions_client_get_mtls_endpoint_and_cert_source(client_class): [ (VersionsClient, transports.VersionsGrpcTransport, "grpc"), (VersionsAsyncClient, transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (VersionsClient, transports.VersionsRestTransport, "rest"), ], ) def test_versions_client_client_options_scopes( @@ -552,6 +575,7 @@ def test_versions_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (VersionsClient, transports.VersionsRestTransport, "rest", None), ], ) def test_versions_client_client_options_credentials_file( @@ -2492,141 +2516,2173 @@ async def test_compare_versions_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.VersionsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + version.ListVersionsRequest, + dict, + ], +) +def test_list_versions_rest(request_type): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = VersionsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.VersionsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_versions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListVersionsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_versions_rest_required_fields(request_type=version.ListVersionsRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_versions._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = VersionsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_versions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_versions_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_versions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.VersionsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_versions_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = VersionsClient( - client_options=options, - transport=transport, + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_list_versions" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_list_versions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.ListVersionsRequest.pb(version.ListVersionsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.ListVersionsResponse.to_json( + version.ListVersionsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = VersionsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = version.ListVersionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.ListVersionsResponse() + + client.list_versions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.VersionsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_versions_rest_bad_request( + transport: str = "rest", request_type=version.ListVersionsRequest +): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = VersionsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.VersionsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_versions(request) + + +def test_list_versions_rest_flattened(): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = VersionsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.VersionsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_versions(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/versions" + % client.transport._host, + args[1], + ) + + +def test_list_versions_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.VersionsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_versions( + version.ListVersionsRequest(), + parent="parent_value", + ) + + +def test_list_versions_rest_pager(transport: str = "rest"): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + version.ListVersionsResponse( + versions=[ + version.Version(), + version.Version(), + version.Version(), + ], + next_page_token="abc", + ), + version.ListVersionsResponse( + versions=[], + next_page_token="def", + ), + version.ListVersionsResponse( + versions=[ + version.Version(), + ], + next_page_token="ghi", + ), + version.ListVersionsResponse( + versions=[ + version.Version(), + version.Version(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(version.ListVersionsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.VersionsGrpcTransport, - transports.VersionsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_versions(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, version.Version) for i in results) + + pages = list(client.list_versions(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + version.GetVersionRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = VersionsClient.get_transport_class(transport_name)( +def test_get_version_rest(request_type): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.Version( + name="name_value", + display_name="display_name_value", + description="description_value", + state=version.Version.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_version(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, version.Version) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == version.Version.State.RUNNING + + +def test_get_version_rest_required_fields(request_type=version.GetVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert isinstance( - client.transport, - transports.VersionsGrpcTransport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.Version() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) -def test_versions_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.VersionsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_get_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_get_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.GetVersionRequest.pb(version.GetVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.Version.to_json(version.Version()) + + request = version.GetVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.Version() -def test_versions_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.versions.transports.VersionsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.VersionsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_version_rest_bad_request( + transport: str = "rest", request_type=version.GetVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_version(request) + + +def test_get_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.Version() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_get_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_version( + version.GetVersionRequest(), + name="name_value", + ) + + +def test_get_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_version.CreateVersionRequest, + dict, + ], +) +def test_create_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["version"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_version(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_create_version_rest_required_fields( + request_type=gcdc_version.CreateVersionRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_version._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "version", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.VersionsRestInterceptor, "post_create_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_create_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_version.CreateVersionRequest.pb( + gcdc_version.CreateVersionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_version.CreateVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.create_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_version_rest_bad_request( + transport: str = "rest", request_type=gcdc_version.CreateVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["version"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_version(request) + + +def test_create_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + version=gcdc_version.Version(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*/flows/*}/versions" + % client.transport._host, + args[1], + ) + + +def test_create_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_version( + gcdc_version.CreateVersionRequest(), + parent="parent_value", + version=gcdc_version.Version(name="name_value"), + ) + + +def test_create_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_version.UpdateVersionRequest, + dict, + ], +) +def test_update_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + request_init["version"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_version.Version.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_version(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_version.Version) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_version.Version.State.RUNNING + + +def test_update_version_rest_required_fields( + request_type=gcdc_version.UpdateVersionRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_version._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_version._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "version", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_update_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_update_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_version.UpdateVersionRequest.pb( + gcdc_version.UpdateVersionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_version.Version.to_json(gcdc_version.Version()) + + request = gcdc_version.UpdateVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_version.Version() + + client.update_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_version_rest_bad_request( + transport: str = "rest", request_type=gcdc_version.UpdateVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + request_init["version"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_version(request) + + +def test_update_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version() + + # get arguments that satisfy an http rule for this method + sample_request = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + version=gcdc_version.Version(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{version.name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_update_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_version( + gcdc_version.UpdateVersionRequest(), + version=gcdc_version.Version(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.DeleteVersionRequest, + dict, + ], +) +def test_delete_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_version(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_version_rest_required_fields(request_type=version.DeleteVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "pre_delete_version" + ) as pre: + pre.assert_not_called() + pb_message = version.DeleteVersionRequest.pb(version.DeleteVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = version.DeleteVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_version_rest_bad_request( + transport: str = "rest", request_type=version.DeleteVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_version(request) + + +def test_delete_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_version( + version.DeleteVersionRequest(), + name="name_value", + ) + + +def test_delete_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.LoadVersionRequest, + dict, + ], +) +def test_load_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.load_version(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_load_version_rest_required_fields(request_type=version.LoadVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).load_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).load_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.load_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_load_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.load_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_load_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.VersionsRestInterceptor, "post_load_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_load_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.LoadVersionRequest.pb(version.LoadVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = version.LoadVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.load_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_load_version_rest_bad_request( + transport: str = "rest", request_type=version.LoadVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.load_version(request) + + +def test_load_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.load_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/flows/*/versions/*}:load" + % client.transport._host, + args[1], + ) + + +def test_load_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.load_version( + version.LoadVersionRequest(), + name="name_value", + ) + + +def test_load_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.CompareVersionsRequest, + dict, + ], +) +def test_compare_versions_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse( + base_version_content_json="base_version_content_json_value", + target_version_content_json="target_version_content_json_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.compare_versions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, version.CompareVersionsResponse) + assert response.base_version_content_json == "base_version_content_json_value" + assert response.target_version_content_json == "target_version_content_json_value" + + +def test_compare_versions_rest_required_fields( + request_type=version.CompareVersionsRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["base_version"] = "" + request_init["target_version"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).compare_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["baseVersion"] = "base_version_value" + jsonified_request["targetVersion"] = "target_version_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).compare_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "baseVersion" in jsonified_request + assert jsonified_request["baseVersion"] == "base_version_value" + assert "targetVersion" in jsonified_request + assert jsonified_request["targetVersion"] == "target_version_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.compare_versions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_compare_versions_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.compare_versions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "baseVersion", + "targetVersion", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_compare_versions_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_compare_versions" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_compare_versions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.CompareVersionsRequest.pb(version.CompareVersionsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.CompareVersionsResponse.to_json( + version.CompareVersionsResponse() + ) + + request = version.CompareVersionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.CompareVersionsResponse() + + client.compare_versions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_compare_versions_rest_bad_request( + transport: str = "rest", request_type=version.CompareVersionsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.compare_versions(request) + + +def test_compare_versions_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + base_version="base_version_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.compare_versions(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{base_version=projects/*/locations/*/agents/*/flows/*/versions/*}:compareVersions" + % client.transport._host, + args[1], + ) + + +def test_compare_versions_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.compare_versions( + version.CompareVersionsRequest(), + base_version="base_version_value", + ) + + +def test_compare_versions_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = VersionsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = VersionsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = VersionsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.VersionsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.VersionsGrpcTransport, + transports.VersionsGrpcAsyncIOTransport, + transports.VersionsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = VersionsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.VersionsGrpcTransport, + ) + + +def test_versions_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.VersionsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_versions_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.versions.transports.VersionsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.VersionsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_versions", @@ -2741,6 +4797,7 @@ def test_versions_transport_auth_adc(transport_class): [ transports.VersionsGrpcTransport, transports.VersionsGrpcAsyncIOTransport, + transports.VersionsRestTransport, ], ) def test_versions_transport_auth_gdch_credentials(transport_class): @@ -2838,11 +4895,40 @@ def test_versions_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_versions_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.VersionsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_versions_rest_lro_client(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_versions_host_no_port(transport_name): @@ -2853,7 +4939,11 @@ def test_versions_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2861,6 +4951,7 @@ def test_versions_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_versions_host_with_port(transport_name): @@ -2871,7 +4962,51 @@ def test_versions_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_versions_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = VersionsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = VersionsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_versions._session + session2 = client2.transport.list_versions._session + assert session1 != session2 + session1 = client1.transport.get_version._session + session2 = client2.transport.get_version._session + assert session1 != session2 + session1 = client1.transport.create_version._session + session2 = client2.transport.create_version._session + assert session1 != session2 + session1 = client1.transport.update_version._session + session2 = client2.transport.update_version._session + assert session1 != session2 + session1 = client1.transport.delete_version._session + session2 = client2.transport.delete_version._session + assert session1 != session2 + session1 = client1.transport.load_version._session + session2 = client2.transport.load_version._session + assert session1 != session2 + session1 = client1.transport.compare_versions._session + session2 = client2.transport.compare_versions._session + assert session1 != session2 def test_versions_grpc_transport_channel(): @@ -3198,6 +5333,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3915,6 +6336,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3932,6 +6354,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3/test_webhooks.py b/tests/unit/gapic/dialogflowcx_v3/test_webhooks.py index 957ef01e..29174865 100644 --- a/tests/unit/gapic/dialogflowcx_v3/test_webhooks.py +++ b/tests/unit/gapic/dialogflowcx_v3/test_webhooks.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -95,6 +102,7 @@ def test__get_default_mtls_endpoint(): [ (WebhooksClient, "grpc"), (WebhooksAsyncClient, "grpc_asyncio"), + (WebhooksClient, "rest"), ], ) def test_webhooks_client_from_service_account_info(client_class, transport_name): @@ -108,7 +116,11 @@ def test_webhooks_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -116,6 +128,7 @@ def test_webhooks_client_from_service_account_info(client_class, transport_name) [ (transports.WebhooksGrpcTransport, "grpc"), (transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.WebhooksRestTransport, "rest"), ], ) def test_webhooks_client_service_account_always_use_jwt( @@ -141,6 +154,7 @@ def test_webhooks_client_service_account_always_use_jwt( [ (WebhooksClient, "grpc"), (WebhooksAsyncClient, "grpc_asyncio"), + (WebhooksClient, "rest"), ], ) def test_webhooks_client_from_service_account_file(client_class, transport_name): @@ -161,13 +175,18 @@ def test_webhooks_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_webhooks_client_get_transport_class(): transport = WebhooksClient.get_transport_class() available_transports = [ transports.WebhooksGrpcTransport, + transports.WebhooksRestTransport, ] assert transport in available_transports @@ -180,6 +199,7 @@ def test_webhooks_client_get_transport_class(): [ (WebhooksClient, transports.WebhooksGrpcTransport, "grpc"), (WebhooksAsyncClient, transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (WebhooksClient, transports.WebhooksRestTransport, "rest"), ], ) @mock.patch.object( @@ -321,6 +341,8 @@ def test_webhooks_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (WebhooksClient, transports.WebhooksRestTransport, "rest", "true"), + (WebhooksClient, transports.WebhooksRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -510,6 +532,7 @@ def test_webhooks_client_get_mtls_endpoint_and_cert_source(client_class): [ (WebhooksClient, transports.WebhooksGrpcTransport, "grpc"), (WebhooksAsyncClient, transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (WebhooksClient, transports.WebhooksRestTransport, "rest"), ], ) def test_webhooks_client_client_options_scopes( @@ -545,6 +568,7 @@ def test_webhooks_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (WebhooksClient, transports.WebhooksRestTransport, "rest", None), ], ) def test_webhooks_client_client_options_credentials_file( @@ -2032,171 +2056,1648 @@ async def test_delete_webhook_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.WebhooksGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + webhook.ListWebhooksRequest, + dict, + ], +) +def test_list_webhooks_rest(request_type): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = WebhooksClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.WebhooksGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_webhooks(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListWebhooksPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_webhooks_rest_required_fields(request_type=webhook.ListWebhooksRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_webhooks._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_webhooks._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = WebhooksClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_webhooks(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_webhooks_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_webhooks._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.WebhooksGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_webhooks_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = WebhooksClient( - client_options=options, - transport=transport, + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_list_webhooks" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_list_webhooks" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = webhook.ListWebhooksRequest.pb(webhook.ListWebhooksRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = webhook.ListWebhooksResponse.to_json( + webhook.ListWebhooksResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = WebhooksClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = webhook.ListWebhooksRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = webhook.ListWebhooksResponse() + + client.list_webhooks( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.WebhooksGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_webhooks_rest_bad_request( + transport: str = "rest", request_type=webhook.ListWebhooksRequest +): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = WebhooksClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.WebhooksGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_webhooks(request) + + +def test_list_webhooks_rest_flattened(): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = WebhooksClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.WebhooksGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_webhooks(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/webhooks" + % client.transport._host, + args[1], + ) + + +def test_list_webhooks_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.WebhooksGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_webhooks( + webhook.ListWebhooksRequest(), + parent="parent_value", + ) + + +def test_list_webhooks_rest_pager(transport: str = "rest"): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + webhook.Webhook(), + webhook.Webhook(), + ], + next_page_token="abc", + ), + webhook.ListWebhooksResponse( + webhooks=[], + next_page_token="def", + ), + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + ], + next_page_token="ghi", + ), + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + webhook.Webhook(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(webhook.ListWebhooksResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.WebhooksGrpcTransport, - transports.WebhooksGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_webhooks(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, webhook.Webhook) for i in results) + + pages = list(client.list_webhooks(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + webhook.GetWebhookRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = WebhooksClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_webhook_rest(request_type): client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.WebhooksGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) -def test_webhooks_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.WebhooksTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=webhook.Webhook.GenericWebService(uri="uri_value"), ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_webhooks_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3.services.webhooks.transports.WebhooksTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.WebhooksTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_webhook(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_webhooks", - "get_webhook", - "create_webhook", - "update_webhook", - "delete_webhook", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + # Establish that the response is the type that we expect. + assert isinstance(response, webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_get_webhook_rest_required_fields(request_type=webhook.GetWebhookRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + # verify required fields with default values are now present -def test_webhooks_base_transport_with_credentials_file(): + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_get_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_get_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = webhook.GetWebhookRequest.pb(webhook.GetWebhookRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = webhook.Webhook.to_json(webhook.Webhook()) + + request = webhook.GetWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = webhook.Webhook() + + client.get_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_webhook_rest_bad_request( + transport: str = "rest", request_type=webhook.GetWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_webhook(request) + + +def test_get_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_get_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_webhook( + webhook.GetWebhookRequest(), + name="name_value", + ) + + +def test_get_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_webhook.CreateWebhookRequest, + dict, + ], +) +def test_create_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["webhook"] = { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=gcdc_webhook.Webhook.GenericWebService(uri="uri_value"), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_webhook(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_create_webhook_rest_required_fields( + request_type=gcdc_webhook.CreateWebhookRequest, +): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_webhook._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "webhook", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_create_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_create_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_webhook.CreateWebhookRequest.pb( + gcdc_webhook.CreateWebhookRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_webhook.Webhook.to_json(gcdc_webhook.Webhook()) + + request = gcdc_webhook.CreateWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_webhook.Webhook() + + client.create_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_webhook_rest_bad_request( + transport: str = "rest", request_type=gcdc_webhook.CreateWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["webhook"] = { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_webhook(request) + + +def test_create_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + webhook=gcdc_webhook.Webhook(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{parent=projects/*/locations/*/agents/*}/webhooks" + % client.transport._host, + args[1], + ) + + +def test_create_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_webhook( + gcdc_webhook.CreateWebhookRequest(), + parent="parent_value", + webhook=gcdc_webhook.Webhook(name="name_value"), + ) + + +def test_create_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_webhook.UpdateWebhookRequest, + dict, + ], +) +def test_update_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + request_init["webhook"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=gcdc_webhook.Webhook.GenericWebService(uri="uri_value"), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_webhook(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_update_webhook_rest_required_fields( + request_type=gcdc_webhook.UpdateWebhookRequest, +): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_webhook._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("webhook",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_update_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_update_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_webhook.UpdateWebhookRequest.pb( + gcdc_webhook.UpdateWebhookRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_webhook.Webhook.to_json(gcdc_webhook.Webhook()) + + request = gcdc_webhook.UpdateWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_webhook.Webhook() + + client.update_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_webhook_rest_bad_request( + transport: str = "rest", request_type=gcdc_webhook.UpdateWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + request_init["webhook"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_webhook(request) + + +def test_update_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + webhook=gcdc_webhook.Webhook(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{webhook.name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_update_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_webhook( + gcdc_webhook.UpdateWebhookRequest(), + webhook=gcdc_webhook.Webhook(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + webhook.DeleteWebhookRequest, + dict, + ], +) +def test_delete_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_webhook(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_webhook_rest_required_fields(request_type=webhook.DeleteWebhookRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_webhook._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_delete_webhook" + ) as pre: + pre.assert_not_called() + pb_message = webhook.DeleteWebhookRequest.pb(webhook.DeleteWebhookRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = webhook.DeleteWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_webhook_rest_bad_request( + transport: str = "rest", request_type=webhook.DeleteWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_webhook(request) + + +def test_delete_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3/{name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_webhook( + webhook.DeleteWebhookRequest(), + name="name_value", + ) + + +def test_delete_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = WebhooksClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = WebhooksClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = WebhooksClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.WebhooksGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.WebhooksGrpcTransport, + transports.WebhooksGrpcAsyncIOTransport, + transports.WebhooksRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = WebhooksClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.WebhooksGrpcTransport, + ) + + +def test_webhooks_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.WebhooksTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_webhooks_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3.services.webhooks.transports.WebhooksTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.WebhooksTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_webhooks", + "get_webhook", + "create_webhook", + "update_webhook", + "delete_webhook", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_webhooks_base_transport_with_credentials_file(): # Instantiate the base transport with a credentials file with mock.patch.object( google.auth, "load_credentials_from_file", autospec=True @@ -2274,6 +3775,7 @@ def test_webhooks_transport_auth_adc(transport_class): [ transports.WebhooksGrpcTransport, transports.WebhooksGrpcAsyncIOTransport, + transports.WebhooksRestTransport, ], ) def test_webhooks_transport_auth_gdch_credentials(transport_class): @@ -2371,11 +3873,23 @@ def test_webhooks_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_webhooks_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.WebhooksRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_webhooks_host_no_port(transport_name): @@ -2386,7 +3900,11 @@ def test_webhooks_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2394,6 +3912,7 @@ def test_webhooks_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_webhooks_host_with_port(transport_name): @@ -2404,7 +3923,45 @@ def test_webhooks_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_webhooks_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = WebhooksClient( + credentials=creds1, + transport=transport_name, + ) + client2 = WebhooksClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_webhooks._session + session2 = client2.transport.list_webhooks._session + assert session1 != session2 + session1 = client1.transport.get_webhook._session + session2 = client2.transport.get_webhook._session + assert session1 != session2 + session1 = client1.transport.create_webhook._session + session2 = client2.transport.create_webhook._session + assert session1 != session2 + session1 = client1.transport.update_webhook._session + session2 = client2.transport.update_webhook._session + assert session1 != session2 + session1 = client1.transport.delete_webhook._session + session2 = client2.transport.delete_webhook._session + assert session1 != session2 def test_webhooks_grpc_transport_channel(): @@ -2723,6 +4280,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3440,6 +5283,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3457,6 +5301,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_agents.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_agents.py index 08e59988..e50cbfb7 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_agents.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_agents.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -48,7 +55,9 @@ from google.cloud.dialogflowcx_v3beta1.types import advanced_settings from google.cloud.dialogflowcx_v3beta1.types import agent from google.cloud.dialogflowcx_v3beta1.types import agent as gcdc_agent +from google.cloud.dialogflowcx_v3beta1.types import audio_config from google.cloud.dialogflowcx_v3beta1.types import flow +from google.cloud.dialogflowcx_v3beta1.types import gcs from google.cloud.location import locations_pb2 from google.longrunning import operations_pb2 from google.oauth2 import service_account @@ -101,6 +110,7 @@ def test__get_default_mtls_endpoint(): [ (AgentsClient, "grpc"), (AgentsAsyncClient, "grpc_asyncio"), + (AgentsClient, "rest"), ], ) def test_agents_client_from_service_account_info(client_class, transport_name): @@ -114,7 +124,11 @@ def test_agents_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -122,6 +136,7 @@ def test_agents_client_from_service_account_info(client_class, transport_name): [ (transports.AgentsGrpcTransport, "grpc"), (transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.AgentsRestTransport, "rest"), ], ) def test_agents_client_service_account_always_use_jwt(transport_class, transport_name): @@ -145,6 +160,7 @@ def test_agents_client_service_account_always_use_jwt(transport_class, transport [ (AgentsClient, "grpc"), (AgentsAsyncClient, "grpc_asyncio"), + (AgentsClient, "rest"), ], ) def test_agents_client_from_service_account_file(client_class, transport_name): @@ -165,13 +181,18 @@ def test_agents_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_agents_client_get_transport_class(): transport = AgentsClient.get_transport_class() available_transports = [ transports.AgentsGrpcTransport, + transports.AgentsRestTransport, ] assert transport in available_transports @@ -184,6 +205,7 @@ def test_agents_client_get_transport_class(): [ (AgentsClient, transports.AgentsGrpcTransport, "grpc"), (AgentsAsyncClient, transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (AgentsClient, transports.AgentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -323,6 +345,8 @@ def test_agents_client_client_options(client_class, transport_class, transport_n "grpc_asyncio", "false", ), + (AgentsClient, transports.AgentsRestTransport, "rest", "true"), + (AgentsClient, transports.AgentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -508,6 +532,7 @@ def test_agents_client_get_mtls_endpoint_and_cert_source(client_class): [ (AgentsClient, transports.AgentsGrpcTransport, "grpc"), (AgentsAsyncClient, transports.AgentsGrpcAsyncIOTransport, "grpc_asyncio"), + (AgentsClient, transports.AgentsRestTransport, "rest"), ], ) def test_agents_client_client_options_scopes( @@ -543,6 +568,7 @@ def test_agents_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (AgentsClient, transports.AgentsRestTransport, "rest", None), ], ) def test_agents_client_client_options_credentials_file( @@ -2811,258 +2837,2694 @@ async def test_get_agent_validation_result_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.AgentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + agent.ListAgentsRequest, + dict, + ], +) +def test_list_agents_rest(request_type): + client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = AgentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = AgentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = AgentsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = AgentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = AgentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_agents(request) + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListAgentsPager) + assert response.next_page_token == "next_page_token_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - client = AgentsClient(transport=transport) - assert client.transport is transport +def test_list_agents_rest_required_fields(request_type=agent.ListAgentsRequest): + transport_class = transports.AgentsRestTransport -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.AgentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - channel = transport.grpc_channel - assert channel - transport = transports.AgentsGrpcAsyncIOTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - channel = transport.grpc_channel - assert channel + # verify fields with default values are dropped + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_agents._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + # verify required fields with default values are now present + jsonified_request["parent"] = "parent_value" -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = AgentsClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_agents._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) ) - assert transport.kind == transport_name + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_agents(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_agents_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.AgentsGrpcTransport, + + unset_fields = transport.list_agents._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) ) -def test_agents_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.AgentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_agents_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_list_agents" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_list_agents" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ListAgentsRequest.pb(agent.ListAgentsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.ListAgentsResponse.to_json( + agent.ListAgentsResponse() ) + request = agent.ListAgentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.ListAgentsResponse() -def test_agents_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.AgentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_agents( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_agents", - "get_agent", - "create_agent", - "update_agent", - "delete_agent", - "export_agent", - "restore_agent", - "validate_agent", - "get_agent_validation_result", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", - ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) + pre.assert_called_once() + post.assert_called_once() - with pytest.raises(NotImplementedError): - transport.close() - # Additionally, the LRO client (a property) should - # also raise NotImplementedError - with pytest.raises(NotImplementedError): - transport.operations_client +def test_list_agents_rest_bad_request( + transport: str = "rest", request_type=agent.ListAgentsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_agents(request) -def test_agents_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.AgentsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", - ) +def test_list_agents_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) -def test_agents_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.AgentsTransport() - adc.assert_called_once() + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.ListAgentsResponse() + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} -def test_agents_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - AgentsClient() - adc.assert_called_once_with( - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id=None, + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.ListAgentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], -) -def test_agents_transport_auth_adc(transport_class): - # If credentials and host are not provided, the transport class should use - # ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class(quota_project_id="octopus", scopes=["1", "2"]) - adc.assert_called_once_with( - scopes=["1", "2"], - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + client.list_agents(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*}/agents" + % client.transport._host, + args[1], ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.AgentsGrpcTransport, - transports.AgentsGrpcAsyncIOTransport, - ], +def test_list_agents_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_agents( + agent.ListAgentsRequest(), + parent="parent_value", + ) + + +def test_list_agents_rest_pager(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + agent.Agent(), + agent.Agent(), + ], + next_page_token="abc", + ), + agent.ListAgentsResponse( + agents=[], + next_page_token="def", + ), + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + ], + next_page_token="ghi", + ), + agent.ListAgentsResponse( + agents=[ + agent.Agent(), + agent.Agent(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(agent.ListAgentsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2"} + + pager = client.list_agents(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, agent.Agent) for i in results) + + pages = list(client.list_agents(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + agent.GetAgentRequest, + dict, + ], +) +def test_get_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_get_agent_rest_required_fields(request_type=agent.GetAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_get_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_get_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.GetAgentRequest.pb(agent.GetAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.Agent.to_json(agent.Agent()) + + request = agent.GetAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.Agent() + + client.get_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_agent_rest_bad_request( + transport: str = "rest", request_type=agent.GetAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_agent(request) + + +def test_get_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*}" + % client.transport._host, + args[1], + ) + + +def test_get_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_agent( + agent.GetAgentRequest(), + name="name_value", + ) + + +def test_get_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_agent.CreateAgentRequest, + dict, + ], +) +def test_create_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["agent"] = { + "name": "name_value", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_create_agent_rest_required_fields(request_type=gcdc_agent.CreateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_agent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "agent", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_create_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_create_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_agent.CreateAgentRequest.pb(gcdc_agent.CreateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_agent.Agent.to_json(gcdc_agent.Agent()) + + request = gcdc_agent.CreateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_agent.Agent() + + client.create_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_agent_rest_bad_request( + transport: str = "rest", request_type=gcdc_agent.CreateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["agent"] = { + "name": "name_value", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_agent(request) + + +def test_create_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + agent=gcdc_agent.Agent(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*}/agents" + % client.transport._host, + args[1], + ) + + +def test_create_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_agent( + gcdc_agent.CreateAgentRequest(), + parent="parent_value", + agent=gcdc_agent.Agent(name="name_value"), + ) + + +def test_create_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_agent.UpdateAgentRequest, + dict, + ], +) +def test_update_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + request_init["agent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent( + name="name_value", + display_name="display_name_value", + default_language_code="default_language_code_value", + supported_language_codes=["supported_language_codes_value"], + time_zone="time_zone_value", + description="description_value", + avatar_uri="avatar_uri_value", + start_flow="start_flow_value", + security_settings="security_settings_value", + enable_stackdriver_logging=True, + enable_spell_correction=True, + locked=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_agent.Agent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.default_language_code == "default_language_code_value" + assert response.supported_language_codes == ["supported_language_codes_value"] + assert response.time_zone == "time_zone_value" + assert response.description == "description_value" + assert response.avatar_uri == "avatar_uri_value" + assert response.start_flow == "start_flow_value" + assert response.security_settings == "security_settings_value" + assert response.enable_stackdriver_logging is True + assert response.enable_spell_correction is True + assert response.locked is True + + +def test_update_agent_rest_required_fields(request_type=gcdc_agent.UpdateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_agent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("agent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_update_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_update_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_agent.UpdateAgentRequest.pb(gcdc_agent.UpdateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_agent.Agent.to_json(gcdc_agent.Agent()) + + request = gcdc_agent.UpdateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_agent.Agent() + + client.update_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_agent_rest_bad_request( + transport: str = "rest", request_type=gcdc_agent.UpdateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + request_init["agent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3", + "display_name": "display_name_value", + "default_language_code": "default_language_code_value", + "supported_language_codes": [ + "supported_language_codes_value1", + "supported_language_codes_value2", + ], + "time_zone": "time_zone_value", + "description": "description_value", + "avatar_uri": "avatar_uri_value", + "speech_to_text_settings": {"enable_speech_adaptation": True}, + "start_flow": "start_flow_value", + "security_settings": "security_settings_value", + "enable_stackdriver_logging": True, + "enable_spell_correction": True, + "locked": True, + "advanced_settings": { + "audio_export_gcs_destination": {"uri": "uri_value"}, + "logging_settings": { + "enable_stackdriver_logging": True, + "enable_interaction_logging": True, + }, + }, + "text_to_speech_settings": {"synthesize_speech_configs": {}}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_agent(request) + + +def test_update_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_agent.Agent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "agent": {"name": "projects/sample1/locations/sample2/agents/sample3"} + } + + # get truthy value for each flattened field + mock_args = dict( + agent=gcdc_agent.Agent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_agent.Agent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{agent.name=projects/*/locations/*/agents/*}" + % client.transport._host, + args[1], + ) + + +def test_update_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_agent( + gcdc_agent.UpdateAgentRequest(), + agent=gcdc_agent.Agent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.DeleteAgentRequest, + dict, + ], +) +def test_delete_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_agent(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_agent_rest_required_fields(request_type=agent.DeleteAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "pre_delete_agent" + ) as pre: + pre.assert_not_called() + pb_message = agent.DeleteAgentRequest.pb(agent.DeleteAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = agent.DeleteAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_agent_rest_bad_request( + transport: str = "rest", request_type=agent.DeleteAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_agent(request) + + +def test_delete_agent_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_agent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_agent_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_agent( + agent.DeleteAgentRequest(), + name="name_value", + ) + + +def test_delete_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.ExportAgentRequest, + dict, + ], +) +def test_export_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_agent(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_agent_rest_required_fields(request_type=agent.ExportAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.AgentsRestInterceptor, "post_export_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_export_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ExportAgentRequest.pb(agent.ExportAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = agent.ExportAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_agent_rest_bad_request( + transport: str = "rest", request_type=agent.ExportAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_agent(request) + + +def test_export_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.RestoreAgentRequest, + dict, + ], +) +def test_restore_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.restore_agent(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_restore_agent_rest_required_fields(request_type=agent.RestoreAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).restore_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).restore_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.restore_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_restore_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.restore_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_restore_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.AgentsRestInterceptor, "post_restore_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_restore_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.RestoreAgentRequest.pb(agent.RestoreAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = agent.RestoreAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.restore_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_restore_agent_rest_bad_request( + transport: str = "rest", request_type=agent.RestoreAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.restore_agent(request) + + +def test_restore_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.ValidateAgentRequest, + dict, + ], +) +def test_validate_agent_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_agent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.AgentValidationResult) + assert response.name == "name_value" + + +def test_validate_agent_rest_required_fields(request_type=agent.ValidateAgentRequest): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_agent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_agent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_agent_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_agent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_agent_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_validate_agent" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_validate_agent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.ValidateAgentRequest.pb(agent.ValidateAgentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.AgentValidationResult.to_json( + agent.AgentValidationResult() + ) + + request = agent.ValidateAgentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.AgentValidationResult() + + client.validate_agent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_agent_rest_bad_request( + transport: str = "rest", request_type=agent.ValidateAgentRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_agent(request) + + +def test_validate_agent_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + agent.GetAgentValidationResultRequest, + dict, + ], +) +def test_get_agent_validation_result_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_agent_validation_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, agent.AgentValidationResult) + assert response.name == "name_value" + + +def test_get_agent_validation_result_rest_required_fields( + request_type=agent.GetAgentValidationResultRequest, +): + transport_class = transports.AgentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent_validation_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_agent_validation_result._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_agent_validation_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_agent_validation_result_rest_unset_required_fields(): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_agent_validation_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_agent_validation_result_rest_interceptors(null_interceptor): + transport = transports.AgentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.AgentsRestInterceptor(), + ) + client = AgentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.AgentsRestInterceptor, "post_get_agent_validation_result" + ) as post, mock.patch.object( + transports.AgentsRestInterceptor, "pre_get_agent_validation_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = agent.GetAgentValidationResultRequest.pb( + agent.GetAgentValidationResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = agent.AgentValidationResult.to_json( + agent.AgentValidationResult() + ) + + request = agent.GetAgentValidationResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = agent.AgentValidationResult() + + client.get_agent_validation_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_agent_validation_result_rest_bad_request( + transport: str = "rest", request_type=agent.GetAgentValidationResultRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_agent_validation_result(request) + + +def test_get_agent_validation_result_rest_flattened(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = agent.AgentValidationResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/validationResult" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = agent.AgentValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_agent_validation_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/validationResult}" + % client.transport._host, + args[1], + ) + + +def test_get_agent_validation_result_rest_flattened_error(transport: str = "rest"): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_agent_validation_result( + agent.GetAgentValidationResultRequest(), + name="name_value", + ) + + +def test_get_agent_validation_result_rest_error(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = AgentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = AgentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = AgentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = AgentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.AgentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.AgentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + transports.AgentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = AgentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.AgentsGrpcTransport, + ) + + +def test_agents_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.AgentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_agents_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.AgentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_agents", + "get_agent", + "create_agent", + "update_agent", + "delete_agent", + "export_agent", + "restore_agent", + "validate_agent", + "get_agent_validation_result", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Additionally, the LRO client (a property) should + # also raise NotImplementedError + with pytest.raises(NotImplementedError): + transport.operations_client + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_agents_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.AgentsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_agents_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.agents.transports.AgentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.AgentsTransport() + adc.assert_called_once() + + +def test_agents_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + AgentsClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + ], +) +def test_agents_transport_auth_adc(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + adc.assert_called_once_with( + scopes=["1", "2"], + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.AgentsGrpcTransport, + transports.AgentsGrpcAsyncIOTransport, + transports.AgentsRestTransport, + ], ) def test_agents_transport_auth_gdch_credentials(transport_class): host = "https://language.com" @@ -3159,11 +5621,40 @@ def test_agents_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_agents_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.AgentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_agents_rest_lro_client(): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_agents_host_no_port(transport_name): @@ -3174,7 +5665,11 @@ def test_agents_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3182,6 +5677,7 @@ def test_agents_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_agents_host_with_port(transport_name): @@ -3192,7 +5688,57 @@ def test_agents_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_agents_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = AgentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = AgentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_agents._session + session2 = client2.transport.list_agents._session + assert session1 != session2 + session1 = client1.transport.get_agent._session + session2 = client2.transport.get_agent._session + assert session1 != session2 + session1 = client1.transport.create_agent._session + session2 = client2.transport.create_agent._session + assert session1 != session2 + session1 = client1.transport.update_agent._session + session2 = client2.transport.update_agent._session + assert session1 != session2 + session1 = client1.transport.delete_agent._session + session2 = client2.transport.delete_agent._session + assert session1 != session2 + session1 = client1.transport.export_agent._session + session2 = client2.transport.export_agent._session + assert session1 != session2 + session1 = client1.transport.restore_agent._session + session2 = client2.transport.restore_agent._session + assert session1 != session2 + session1 = client1.transport.validate_agent._session + session2 = client2.transport.validate_agent._session + assert session1 != session2 + session1 = client1.transport.get_agent_validation_result._session + session2 = client2.transport.get_agent_validation_result._session + assert session1 != session2 def test_agents_grpc_transport_channel(): @@ -3654,6 +6200,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = AgentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = AgentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4371,6 +7203,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4388,6 +7221,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_changelogs.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_changelogs.py index 076d66f7..49bbeda0 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_changelogs.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_changelogs.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -95,6 +102,7 @@ def test__get_default_mtls_endpoint(): [ (ChangelogsClient, "grpc"), (ChangelogsAsyncClient, "grpc_asyncio"), + (ChangelogsClient, "rest"), ], ) def test_changelogs_client_from_service_account_info(client_class, transport_name): @@ -108,7 +116,11 @@ def test_changelogs_client_from_service_account_info(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -116,6 +128,7 @@ def test_changelogs_client_from_service_account_info(client_class, transport_nam [ (transports.ChangelogsGrpcTransport, "grpc"), (transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ChangelogsRestTransport, "rest"), ], ) def test_changelogs_client_service_account_always_use_jwt( @@ -141,6 +154,7 @@ def test_changelogs_client_service_account_always_use_jwt( [ (ChangelogsClient, "grpc"), (ChangelogsAsyncClient, "grpc_asyncio"), + (ChangelogsClient, "rest"), ], ) def test_changelogs_client_from_service_account_file(client_class, transport_name): @@ -161,13 +175,18 @@ def test_changelogs_client_from_service_account_file(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_changelogs_client_get_transport_class(): transport = ChangelogsClient.get_transport_class() available_transports = [ transports.ChangelogsGrpcTransport, + transports.ChangelogsRestTransport, ] assert transport in available_transports @@ -184,6 +203,7 @@ def test_changelogs_client_get_transport_class(): transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest"), ], ) @mock.patch.object( @@ -327,6 +347,8 @@ def test_changelogs_client_client_options( "grpc_asyncio", "false", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", "true"), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -520,6 +542,7 @@ def test_changelogs_client_get_mtls_endpoint_and_cert_source(client_class): transports.ChangelogsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest"), ], ) def test_changelogs_client_client_options_scopes( @@ -555,6 +578,7 @@ def test_changelogs_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (ChangelogsClient, transports.ChangelogsRestTransport, "rest", None), ], ) def test_changelogs_client_client_options_credentials_file( @@ -1331,6 +1355,624 @@ async def test_get_changelog_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + changelog.ListChangelogsRequest, + dict, + ], +) +def test_list_changelogs_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_changelogs(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListChangelogsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_changelogs_rest_required_fields( + request_type=changelog.ListChangelogsRequest, +): + transport_class = transports.ChangelogsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_changelogs._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_changelogs._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "filter", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_changelogs(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_changelogs_rest_unset_required_fields(): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_changelogs._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "filter", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_changelogs_rest_interceptors(null_interceptor): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ChangelogsRestInterceptor(), + ) + client = ChangelogsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ChangelogsRestInterceptor, "post_list_changelogs" + ) as post, mock.patch.object( + transports.ChangelogsRestInterceptor, "pre_list_changelogs" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = changelog.ListChangelogsRequest.pb( + changelog.ListChangelogsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = changelog.ListChangelogsResponse.to_json( + changelog.ListChangelogsResponse() + ) + + request = changelog.ListChangelogsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = changelog.ListChangelogsResponse() + + client.list_changelogs( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_changelogs_rest_bad_request( + transport: str = "rest", request_type=changelog.ListChangelogsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_changelogs(request) + + +def test_list_changelogs_rest_flattened(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.ListChangelogsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.ListChangelogsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_changelogs(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/changelogs" + % client.transport._host, + args[1], + ) + + +def test_list_changelogs_rest_flattened_error(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_changelogs( + changelog.ListChangelogsRequest(), + parent="parent_value", + ) + + +def test_list_changelogs_rest_pager(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + changelog.Changelog(), + changelog.Changelog(), + ], + next_page_token="abc", + ), + changelog.ListChangelogsResponse( + changelogs=[], + next_page_token="def", + ), + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + ], + next_page_token="ghi", + ), + changelog.ListChangelogsResponse( + changelogs=[ + changelog.Changelog(), + changelog.Changelog(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(changelog.ListChangelogsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_changelogs(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, changelog.Changelog) for i in results) + + pages = list(client.list_changelogs(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + changelog.GetChangelogRequest, + dict, + ], +) +def test_get_changelog_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog( + name="name_value", + user_email="user_email_value", + display_name="display_name_value", + action="action_value", + type_="type__value", + resource="resource_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_changelog(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, changelog.Changelog) + assert response.name == "name_value" + assert response.user_email == "user_email_value" + assert response.display_name == "display_name_value" + assert response.action == "action_value" + assert response.type_ == "type__value" + assert response.resource == "resource_value" + + +def test_get_changelog_rest_required_fields(request_type=changelog.GetChangelogRequest): + transport_class = transports.ChangelogsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_changelog._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_changelog._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_changelog(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_changelog_rest_unset_required_fields(): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_changelog._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_changelog_rest_interceptors(null_interceptor): + transport = transports.ChangelogsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ChangelogsRestInterceptor(), + ) + client = ChangelogsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ChangelogsRestInterceptor, "post_get_changelog" + ) as post, mock.patch.object( + transports.ChangelogsRestInterceptor, "pre_get_changelog" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = changelog.GetChangelogRequest.pb(changelog.GetChangelogRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = changelog.Changelog.to_json(changelog.Changelog()) + + request = changelog.GetChangelogRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = changelog.Changelog() + + client.get_changelog( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_changelog_rest_bad_request( + transport: str = "rest", request_type=changelog.GetChangelogRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_changelog(request) + + +def test_get_changelog_rest_flattened(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = changelog.Changelog() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/changelogs/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = changelog.Changelog.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_changelog(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/changelogs/*}" + % client.transport._host, + args[1], + ) + + +def test_get_changelog_rest_flattened_error(transport: str = "rest"): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_changelog( + changelog.GetChangelogRequest(), + name="name_value", + ) + + +def test_get_changelog_rest_error(): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.ChangelogsGrpcTransport( @@ -1412,6 +2054,7 @@ def test_transport_get_channel(): [ transports.ChangelogsGrpcTransport, transports.ChangelogsGrpcAsyncIOTransport, + transports.ChangelogsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1426,6 +2069,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1570,6 +2214,7 @@ def test_changelogs_transport_auth_adc(transport_class): [ transports.ChangelogsGrpcTransport, transports.ChangelogsGrpcAsyncIOTransport, + transports.ChangelogsRestTransport, ], ) def test_changelogs_transport_auth_gdch_credentials(transport_class): @@ -1667,11 +2312,23 @@ def test_changelogs_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_changelogs_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ChangelogsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_changelogs_host_no_port(transport_name): @@ -1682,7 +2339,11 @@ def test_changelogs_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1690,6 +2351,7 @@ def test_changelogs_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_changelogs_host_with_port(transport_name): @@ -1700,7 +2362,36 @@ def test_changelogs_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_changelogs_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ChangelogsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ChangelogsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_changelogs._session + session2 = client2.transport.list_changelogs._session + assert session1 != session2 + session1 = client1.transport.get_changelog._session + session2 = client2.transport.get_changelog._session + assert session1 != session2 def test_changelogs_grpc_transport_channel(): @@ -1990,6 +2681,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = ChangelogsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = ChangelogsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2707,6 +3684,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2724,6 +3702,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_deployments.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_deployments.py index fc51f3f2..d6267084 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_deployments.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_deployments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -97,6 +104,7 @@ def test__get_default_mtls_endpoint(): [ (DeploymentsClient, "grpc"), (DeploymentsAsyncClient, "grpc_asyncio"), + (DeploymentsClient, "rest"), ], ) def test_deployments_client_from_service_account_info(client_class, transport_name): @@ -110,7 +118,11 @@ def test_deployments_client_from_service_account_info(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -118,6 +130,7 @@ def test_deployments_client_from_service_account_info(client_class, transport_na [ (transports.DeploymentsGrpcTransport, "grpc"), (transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.DeploymentsRestTransport, "rest"), ], ) def test_deployments_client_service_account_always_use_jwt( @@ -143,6 +156,7 @@ def test_deployments_client_service_account_always_use_jwt( [ (DeploymentsClient, "grpc"), (DeploymentsAsyncClient, "grpc_asyncio"), + (DeploymentsClient, "rest"), ], ) def test_deployments_client_from_service_account_file(client_class, transport_name): @@ -163,13 +177,18 @@ def test_deployments_client_from_service_account_file(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_deployments_client_get_transport_class(): transport = DeploymentsClient.get_transport_class() available_transports = [ transports.DeploymentsGrpcTransport, + transports.DeploymentsRestTransport, ] assert transport in available_transports @@ -186,6 +205,7 @@ def test_deployments_client_get_transport_class(): transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -329,6 +349,8 @@ def test_deployments_client_client_options( "grpc_asyncio", "false", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", "true"), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -522,6 +544,7 @@ def test_deployments_client_get_mtls_endpoint_and_cert_source(client_class): transports.DeploymentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest"), ], ) def test_deployments_client_client_options_scopes( @@ -557,6 +580,7 @@ def test_deployments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (DeploymentsClient, transports.DeploymentsRestTransport, "rest", None), ], ) def test_deployments_client_client_options_credentials_file( @@ -1325,6 +1349,632 @@ async def test_get_deployment_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + deployment.ListDeploymentsRequest, + dict, + ], +) +def test_list_deployments_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_deployments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListDeploymentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_deployments_rest_required_fields( + request_type=deployment.ListDeploymentsRequest, +): + transport_class = transports.DeploymentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_deployments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_deployments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_deployments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_deployments_rest_unset_required_fields(): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_deployments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_deployments_rest_interceptors(null_interceptor): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.DeploymentsRestInterceptor(), + ) + client = DeploymentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.DeploymentsRestInterceptor, "post_list_deployments" + ) as post, mock.patch.object( + transports.DeploymentsRestInterceptor, "pre_list_deployments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = deployment.ListDeploymentsRequest.pb( + deployment.ListDeploymentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = deployment.ListDeploymentsResponse.to_json( + deployment.ListDeploymentsResponse() + ) + + request = deployment.ListDeploymentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = deployment.ListDeploymentsResponse() + + client.list_deployments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_deployments_rest_bad_request( + transport: str = "rest", request_type=deployment.ListDeploymentsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_deployments(request) + + +def test_list_deployments_rest_flattened(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.ListDeploymentsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.ListDeploymentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_deployments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/deployments" + % client.transport._host, + args[1], + ) + + +def test_list_deployments_rest_flattened_error(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_deployments( + deployment.ListDeploymentsRequest(), + parent="parent_value", + ) + + +def test_list_deployments_rest_pager(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + deployment.Deployment(), + deployment.Deployment(), + ], + next_page_token="abc", + ), + deployment.ListDeploymentsResponse( + deployments=[], + next_page_token="def", + ), + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + ], + next_page_token="ghi", + ), + deployment.ListDeploymentsResponse( + deployments=[ + deployment.Deployment(), + deployment.Deployment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + deployment.ListDeploymentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.list_deployments(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, deployment.Deployment) for i in results) + + pages = list(client.list_deployments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + deployment.GetDeploymentRequest, + dict, + ], +) +def test_get_deployment_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment( + name="name_value", + flow_version="flow_version_value", + state=deployment.Deployment.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_deployment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, deployment.Deployment) + assert response.name == "name_value" + assert response.flow_version == "flow_version_value" + assert response.state == deployment.Deployment.State.RUNNING + + +def test_get_deployment_rest_required_fields( + request_type=deployment.GetDeploymentRequest, +): + transport_class = transports.DeploymentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_deployment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_deployment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_deployment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_deployment_rest_unset_required_fields(): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_deployment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_deployment_rest_interceptors(null_interceptor): + transport = transports.DeploymentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.DeploymentsRestInterceptor(), + ) + client = DeploymentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.DeploymentsRestInterceptor, "post_get_deployment" + ) as post, mock.patch.object( + transports.DeploymentsRestInterceptor, "pre_get_deployment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = deployment.GetDeploymentRequest.pb( + deployment.GetDeploymentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = deployment.Deployment.to_json( + deployment.Deployment() + ) + + request = deployment.GetDeploymentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = deployment.Deployment() + + client.get_deployment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_deployment_rest_bad_request( + transport: str = "rest", request_type=deployment.GetDeploymentRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_deployment(request) + + +def test_get_deployment_rest_flattened(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = deployment.Deployment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/deployments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = deployment.Deployment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_deployment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/deployments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_deployment_rest_flattened_error(transport: str = "rest"): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_deployment( + deployment.GetDeploymentRequest(), + name="name_value", + ) + + +def test_get_deployment_rest_error(): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.DeploymentsGrpcTransport( @@ -1406,6 +2056,7 @@ def test_transport_get_channel(): [ transports.DeploymentsGrpcTransport, transports.DeploymentsGrpcAsyncIOTransport, + transports.DeploymentsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1420,6 +2071,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1564,6 +2216,7 @@ def test_deployments_transport_auth_adc(transport_class): [ transports.DeploymentsGrpcTransport, transports.DeploymentsGrpcAsyncIOTransport, + transports.DeploymentsRestTransport, ], ) def test_deployments_transport_auth_gdch_credentials(transport_class): @@ -1661,11 +2314,23 @@ def test_deployments_grpc_transport_client_cert_source_for_mtls(transport_class) ) +def test_deployments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.DeploymentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_deployments_host_no_port(transport_name): @@ -1676,7 +2341,11 @@ def test_deployments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1684,6 +2353,7 @@ def test_deployments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_deployments_host_with_port(transport_name): @@ -1694,7 +2364,36 @@ def test_deployments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_deployments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = DeploymentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = DeploymentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_deployments._session + session2 = client2.transport.list_deployments._session + assert session1 != session2 + session1 = client1.transport.get_deployment._session + session2 = client2.transport.get_deployment._session + assert session1 != session2 def test_deployments_grpc_transport_channel(): @@ -2089,6 +2788,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = DeploymentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = DeploymentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2806,6 +3791,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2823,6 +3809,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_entity_types.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_entity_types.py index 6a24970b..910af343 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_entity_types.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_entity_types.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -98,6 +105,7 @@ def test__get_default_mtls_endpoint(): [ (EntityTypesClient, "grpc"), (EntityTypesAsyncClient, "grpc_asyncio"), + (EntityTypesClient, "rest"), ], ) def test_entity_types_client_from_service_account_info(client_class, transport_name): @@ -111,7 +119,11 @@ def test_entity_types_client_from_service_account_info(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -119,6 +131,7 @@ def test_entity_types_client_from_service_account_info(client_class, transport_n [ (transports.EntityTypesGrpcTransport, "grpc"), (transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.EntityTypesRestTransport, "rest"), ], ) def test_entity_types_client_service_account_always_use_jwt( @@ -144,6 +157,7 @@ def test_entity_types_client_service_account_always_use_jwt( [ (EntityTypesClient, "grpc"), (EntityTypesAsyncClient, "grpc_asyncio"), + (EntityTypesClient, "rest"), ], ) def test_entity_types_client_from_service_account_file(client_class, transport_name): @@ -164,13 +178,18 @@ def test_entity_types_client_from_service_account_file(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_entity_types_client_get_transport_class(): transport = EntityTypesClient.get_transport_class() available_transports = [ transports.EntityTypesGrpcTransport, + transports.EntityTypesRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_entity_types_client_get_transport_class(): transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest"), ], ) @mock.patch.object( @@ -330,6 +350,8 @@ def test_entity_types_client_client_options( "grpc_asyncio", "false", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", "true"), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -523,6 +545,7 @@ def test_entity_types_client_get_mtls_endpoint_and_cert_source(client_class): transports.EntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest"), ], ) def test_entity_types_client_client_options_scopes( @@ -558,6 +581,7 @@ def test_entity_types_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (EntityTypesClient, transports.EntityTypesRestTransport, "rest", None), ], ) def test_entity_types_client_client_options_credentials_file( @@ -2166,211 +2190,1753 @@ async def test_delete_entity_type_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.EntityTypesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + entity_type.ListEntityTypesRequest, + dict, + ], +) +def test_list_entity_types_rest(request_type): + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = EntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_entity_types(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListEntityTypesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_entity_types_rest_required_fields( + request_type=entity_type.ListEntityTypesRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = EntityTypesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_entity_types._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_entity_types._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.EntityTypesGrpcTransport( + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = EntityTypesClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.EntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_entity_types(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_entity_types_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.EntityTypesGrpcAsyncIOTransport( + unset_fields = transport.list_entity_types._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_entity_types_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_list_entity_types" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_list_entity_types" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = entity_type.ListEntityTypesRequest.pb( + entity_type.ListEntityTypesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = entity_type.ListEntityTypesResponse.to_json( + entity_type.ListEntityTypesResponse() + ) + request = entity_type.ListEntityTypesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = entity_type.ListEntityTypesResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.EntityTypesGrpcTransport, - transports.EntityTypesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_entity_types( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = EntityTypesClient.get_transport_class(transport_name)( + +def test_list_entity_types_rest_bad_request( + transport: str = "rest", request_type=entity_type.ListEntityTypesRequest +): + client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_entity_types(request) + + +def test_list_entity_types_rest_flattened(): client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.EntityTypesGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.ListEntityTypesResponse() -def test_entity_types_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.EntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.ListEntityTypesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -def test_entity_types_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.EntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_entity_types(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/entityTypes" + % client.transport._host, + args[1], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_entity_types", - "get_entity_type", - "create_entity_type", - "update_entity_type", - "delete_entity_type", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + +def test_list_entity_types_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_entity_types( + entity_type.ListEntityTypesRequest(), + parent="parent_value", + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_entity_types_rest_pager(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_entity_types_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.EntityTypesTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + entity_type.EntityType(), + entity_type.EntityType(), + ], + next_page_token="abc", + ), + entity_type.ListEntityTypesResponse( + entity_types=[], + next_page_token="def", + ), + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + ], + next_page_token="ghi", + ), + entity_type.ListEntityTypesResponse( + entity_types=[ + entity_type.EntityType(), + entity_type.EntityType(), + ], ), - quota_project_id="octopus", ) + # Two responses for two calls + response = response + response + # Wrap the values into proper Response objs + response = tuple( + entity_type.ListEntityTypesResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -def test_entity_types_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.EntityTypesTransport() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + pager = client.list_entity_types(request=sample_request) -def test_entity_types_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - EntityTypesClient() - adc.assert_called_once_with( + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, entity_type.EntityType) for i in results) + + pages = list(client.list_entity_types(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + entity_type.GetEntityTypeRequest, + dict, + ], +) +def test_get_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_get_entity_type_rest_required_fields( + request_type=entity_type.GetEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_get_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_get_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = entity_type.GetEntityTypeRequest.pb( + entity_type.GetEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = entity_type.EntityType.to_json( + entity_type.EntityType() + ) + + request = entity_type.GetEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = entity_type.EntityType() + + client.get_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_entity_type_rest_bad_request( + transport: str = "rest", request_type=entity_type.GetEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_entity_type(request) + + +def test_get_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_get_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_entity_type( + entity_type.GetEntityTypeRequest(), + name="name_value", + ) + + +def test_get_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_entity_type.CreateEntityTypeRequest, + dict, + ], +) +def test_create_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["entity_type"] = { + "name": "name_value", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=gcdc_entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == gcdc_entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_create_entity_type_rest_required_fields( + request_type=gcdc_entity_type.CreateEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "entityType", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_create_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_create_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_entity_type.CreateEntityTypeRequest.pb( + gcdc_entity_type.CreateEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_entity_type.EntityType.to_json( + gcdc_entity_type.EntityType() + ) + + request = gcdc_entity_type.CreateEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_entity_type.EntityType() + + client.create_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_entity_type_rest_bad_request( + transport: str = "rest", request_type=gcdc_entity_type.CreateEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["entity_type"] = { + "name": "name_value", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_entity_type(request) + + +def test_create_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + entity_type=gcdc_entity_type.EntityType(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/entityTypes" + % client.transport._host, + args[1], + ) + + +def test_create_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_entity_type( + gcdc_entity_type.CreateEntityTypeRequest(), + parent="parent_value", + entity_type=gcdc_entity_type.EntityType(name="name_value"), + ) + + +def test_create_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_entity_type.UpdateEntityTypeRequest, + dict, + ], +) +def test_update_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + request_init["entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType( + name="name_value", + display_name="display_name_value", + kind=gcdc_entity_type.EntityType.Kind.KIND_MAP, + auto_expansion_mode=gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT, + enable_fuzzy_extraction=True, + redact=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_entity_type.EntityType) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.kind == gcdc_entity_type.EntityType.Kind.KIND_MAP + assert ( + response.auto_expansion_mode + == gcdc_entity_type.EntityType.AutoExpansionMode.AUTO_EXPANSION_MODE_DEFAULT + ) + assert response.enable_fuzzy_extraction is True + assert response.redact is True + + +def test_update_entity_type_rest_required_fields( + request_type=gcdc_entity_type.UpdateEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("entityType",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "post_update_entity_type" + ) as post, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_update_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_entity_type.UpdateEntityTypeRequest.pb( + gcdc_entity_type.UpdateEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_entity_type.EntityType.to_json( + gcdc_entity_type.EntityType() + ) + + request = gcdc_entity_type.UpdateEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_entity_type.EntityType() + + client.update_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_entity_type_rest_bad_request( + transport: str = "rest", request_type=gcdc_entity_type.UpdateEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + request_init["entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4", + "display_name": "display_name_value", + "kind": 1, + "auto_expansion_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + "excluded_phrases": [{"value": "value_value"}], + "enable_fuzzy_extraction": True, + "redact": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_entity_type(request) + + +def test_update_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_entity_type.EntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + entity_type=gcdc_entity_type.EntityType(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_entity_type.EntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{entity_type.name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_entity_type( + gcdc_entity_type.UpdateEntityTypeRequest(), + entity_type=gcdc_entity_type.EntityType(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + entity_type.DeleteEntityTypeRequest, + dict, + ], +) +def test_delete_entity_type_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_entity_type(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_entity_type_rest_required_fields( + request_type=entity_type.DeleteEntityTypeRequest, +): + transport_class = transports.EntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_entity_type_rest_unset_required_fields(): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_entity_type_rest_interceptors(null_interceptor): + transport = transports.EntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EntityTypesRestInterceptor(), + ) + client = EntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EntityTypesRestInterceptor, "pre_delete_entity_type" + ) as pre: + pre.assert_not_called() + pb_message = entity_type.DeleteEntityTypeRequest.pb( + entity_type.DeleteEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = entity_type.DeleteEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_entity_type_rest_bad_request( + transport: str = "rest", request_type=entity_type.DeleteEntityTypeRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_entity_type(request) + + +def test_delete_entity_type_rest_flattened(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/entityTypes/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_entity_type_rest_flattened_error(transport: str = "rest"): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_entity_type( + entity_type.DeleteEntityTypeRequest(), + name="name_value", + ) + + +def test_delete_entity_type_rest_error(): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EntityTypesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = EntityTypesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.EntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.EntityTypesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.EntityTypesGrpcTransport, + transports.EntityTypesGrpcAsyncIOTransport, + transports.EntityTypesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = EntityTypesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.EntityTypesGrpcTransport, + ) + + +def test_entity_types_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.EntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_entity_types_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.EntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_entity_types", + "get_entity_type", + "create_entity_type", + "update_entity_type", + "delete_entity_type", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_entity_types_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.EntityTypesTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_entity_types_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.entity_types.transports.EntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.EntityTypesTransport() + adc.assert_called_once() + + +def test_entity_types_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + EntityTypesClient() + adc.assert_called_once_with( scopes=None, default_scopes=( "https://www.googleapis.com/auth/cloud-platform", @@ -2408,6 +3974,7 @@ def test_entity_types_transport_auth_adc(transport_class): [ transports.EntityTypesGrpcTransport, transports.EntityTypesGrpcAsyncIOTransport, + transports.EntityTypesRestTransport, ], ) def test_entity_types_transport_auth_gdch_credentials(transport_class): @@ -2505,11 +4072,23 @@ def test_entity_types_grpc_transport_client_cert_source_for_mtls(transport_class ) +def test_entity_types_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.EntityTypesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_entity_types_host_no_port(transport_name): @@ -2520,7 +4099,11 @@ def test_entity_types_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2528,6 +4111,7 @@ def test_entity_types_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_entity_types_host_with_port(transport_name): @@ -2538,7 +4122,45 @@ def test_entity_types_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_entity_types_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = EntityTypesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = EntityTypesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_entity_types._session + session2 = client2.transport.list_entity_types._session + assert session1 != session2 + session1 = client1.transport.get_entity_type._session + session2 = client2.transport.get_entity_type._session + assert session1 != session2 + session1 = client1.transport.create_entity_type._session + session2 = client2.transport.create_entity_type._session + assert session1 != session2 + session1 = client1.transport.update_entity_type._session + session2 = client2.transport.update_entity_type._session + assert session1 != session2 + session1 = client1.transport.delete_entity_type._session + session2 = client2.transport.delete_entity_type._session + assert session1 != session2 def test_entity_types_grpc_transport_channel(): @@ -2828,6 +4450,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = EntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = EntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3545,6 +5453,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3562,6 +5471,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_environments.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_environments.py index 7c28e919..a43c91fd 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_environments.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_environments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -106,6 +113,7 @@ def test__get_default_mtls_endpoint(): [ (EnvironmentsClient, "grpc"), (EnvironmentsAsyncClient, "grpc_asyncio"), + (EnvironmentsClient, "rest"), ], ) def test_environments_client_from_service_account_info(client_class, transport_name): @@ -119,7 +127,11 @@ def test_environments_client_from_service_account_info(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -127,6 +139,7 @@ def test_environments_client_from_service_account_info(client_class, transport_n [ (transports.EnvironmentsGrpcTransport, "grpc"), (transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.EnvironmentsRestTransport, "rest"), ], ) def test_environments_client_service_account_always_use_jwt( @@ -152,6 +165,7 @@ def test_environments_client_service_account_always_use_jwt( [ (EnvironmentsClient, "grpc"), (EnvironmentsAsyncClient, "grpc_asyncio"), + (EnvironmentsClient, "rest"), ], ) def test_environments_client_from_service_account_file(client_class, transport_name): @@ -172,13 +186,18 @@ def test_environments_client_from_service_account_file(client_class, transport_n assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_environments_client_get_transport_class(): transport = EnvironmentsClient.get_transport_class() available_transports = [ transports.EnvironmentsGrpcTransport, + transports.EnvironmentsRestTransport, ] assert transport in available_transports @@ -195,6 +214,7 @@ def test_environments_client_get_transport_class(): transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -338,6 +358,8 @@ def test_environments_client_client_options( "grpc_asyncio", "false", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", "true"), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -531,6 +553,7 @@ def test_environments_client_get_mtls_endpoint_and_cert_source(client_class): transports.EnvironmentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest"), ], ) def test_environments_client_client_options_scopes( @@ -571,6 +594,7 @@ def test_environments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (EnvironmentsClient, transports.EnvironmentsRestTransport, "rest", None), ], ) def test_environments_client_client_options_credentials_file( @@ -3286,141 +3310,2859 @@ async def test_deploy_flow_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.EnvironmentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + environment.ListEnvironmentsRequest, + dict, + ], +) +def test_list_environments_rest(request_type): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_environments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListEnvironmentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_environments_rest_required_fields( + request_type=environment.ListEnvironmentsRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_environments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_environments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_environments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_environments_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + + unset_fields = transport.list_environments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_environments_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options=options, - transport=transport, + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_list_environments" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_list_environments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.ListEnvironmentsRequest.pb( + environment.ListEnvironmentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = environment.ListEnvironmentsResponse.to_json( + environment.ListEnvironmentsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = environment.ListEnvironmentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.ListEnvironmentsResponse() + + client.list_environments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.EnvironmentsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_environments_rest_bad_request( + transport: str = "rest", request_type=environment.ListEnvironmentsRequest +): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = EnvironmentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.EnvironmentsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_environments(request) + + +def test_list_environments_rest_flattened(): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = EnvironmentsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListEnvironmentsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.EnvironmentsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListEnvironmentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_environments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/environments" + % client.transport._host, + args[1], + ) + + +def test_list_environments_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.EnvironmentsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_environments( + environment.ListEnvironmentsRequest(), + parent="parent_value", + ) + + +def test_list_environments_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + environment.Environment(), + environment.Environment(), + ], + next_page_token="abc", + ), + environment.ListEnvironmentsResponse( + environments=[], + next_page_token="def", + ), + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + ], + next_page_token="ghi", + ), + environment.ListEnvironmentsResponse( + environments=[ + environment.Environment(), + environment.Environment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.ListEnvironmentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.EnvironmentsGrpcTransport, - transports.EnvironmentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_environments(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.Environment) for i in results) + + pages = list(client.list_environments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + environment.GetEnvironmentRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = EnvironmentsClient.get_transport_class(transport_name)( +def test_get_environment_rest(request_type): + client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.Environment( + name="name_value", + display_name="display_name_value", + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_environment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, environment.Environment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + + +def test_get_environment_rest_required_fields( + request_type=environment.GetEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.EnvironmentsGrpcTransport, + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.Environment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_environment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) -def test_environments_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.EnvironmentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_get_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_get_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.GetEnvironmentRequest.pb( + environment.GetEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = environment.Environment.to_json( + environment.Environment() ) + request = environment.GetEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.Environment() -def test_environments_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.environments.transports.EnvironmentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.EnvironmentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_environment_rest_bad_request( + transport: str = "rest", request_type=environment.GetEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_environment(request) + + +def test_get_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.Environment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.Environment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_environment( + environment.GetEnvironmentRequest(), + name="name_value", + ) + + +def test_get_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_environment.CreateEnvironmentRequest, + dict, + ], +) +def test_create_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["environment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_environment(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_create_environment_rest_required_fields( + request_type=gcdc_environment.CreateEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_environment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "environment", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_create_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_create_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_environment.CreateEnvironmentRequest.pb( + gcdc_environment.CreateEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_environment.CreateEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.create_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_environment_rest_bad_request( + transport: str = "rest", request_type=gcdc_environment.CreateEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["environment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_environment(request) + + +def test_create_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + environment=gcdc_environment.Environment(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/environments" + % client.transport._host, + args[1], + ) + + +def test_create_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_environment( + gcdc_environment.CreateEnvironmentRequest(), + parent="parent_value", + environment=gcdc_environment.Environment(name="name_value"), + ) + + +def test_create_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_environment.UpdateEnvironmentRequest, + dict, + ], +) +def test_update_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + request_init["environment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_environment(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_update_environment_rest_required_fields( + request_type=gcdc_environment.UpdateEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_environment._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_environment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "environment", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_update_environment" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_update_environment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_environment.UpdateEnvironmentRequest.pb( + gcdc_environment.UpdateEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_environment.UpdateEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.update_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_environment_rest_bad_request( + transport: str = "rest", request_type=gcdc_environment.UpdateEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + request_init["environment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4", + "display_name": "display_name_value", + "description": "description_value", + "version_configs": [{"version": "version_value"}], + "update_time": {"seconds": 751, "nanos": 543}, + "test_cases_config": { + "test_cases": ["test_cases_value1", "test_cases_value2"], + "enable_continuous_run": True, + "enable_predeployment_run": True, + }, + "webhook_config": { + "webhook_overrides": [ + { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [ + b"allowed_ca_certs_blob1", + b"allowed_ca_certs_blob2", + ], + }, + "service_directory": { + "service": "service_value", + "generic_web_service": {}, + }, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + ] + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_environment(request) + + +def test_update_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "environment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + environment=gcdc_environment.Environment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{environment.name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_update_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_environment( + gcdc_environment.UpdateEnvironmentRequest(), + environment=gcdc_environment.Environment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.DeleteEnvironmentRequest, + dict, + ], +) +def test_delete_environment_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_environment(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_environment_rest_required_fields( + request_type=environment.DeleteEnvironmentRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_environment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_environment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_environment_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_environment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_environment_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_delete_environment" + ) as pre: + pre.assert_not_called() + pb_message = environment.DeleteEnvironmentRequest.pb( + environment.DeleteEnvironmentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = environment.DeleteEnvironmentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_environment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_environment_rest_bad_request( + transport: str = "rest", request_type=environment.DeleteEnvironmentRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_environment(request) + + +def test_delete_environment_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_environment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_environment_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_environment( + environment.DeleteEnvironmentRequest(), + name="name_value", + ) + + +def test_delete_environment_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.LookupEnvironmentHistoryRequest, + dict, + ], +) +def test_lookup_environment_history_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.lookup_environment_history(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.LookupEnvironmentHistoryPager) + assert response.next_page_token == "next_page_token_value" + + +def test_lookup_environment_history_rest_required_fields( + request_type=environment.LookupEnvironmentHistoryRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).lookup_environment_history._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).lookup_environment_history._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.lookup_environment_history(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_lookup_environment_history_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.lookup_environment_history._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("name",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_lookup_environment_history_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_lookup_environment_history" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_lookup_environment_history" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.LookupEnvironmentHistoryRequest.pb( + environment.LookupEnvironmentHistoryRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + environment.LookupEnvironmentHistoryResponse.to_json( + environment.LookupEnvironmentHistoryResponse() + ) + ) + + request = environment.LookupEnvironmentHistoryRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.LookupEnvironmentHistoryResponse() + + client.lookup_environment_history( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_lookup_environment_history_rest_bad_request( + transport: str = "rest", request_type=environment.LookupEnvironmentHistoryRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.lookup_environment_history(request) + + +def test_lookup_environment_history_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.LookupEnvironmentHistoryResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.LookupEnvironmentHistoryResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.lookup_environment_history(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*}:lookupEnvironmentHistory" + % client.transport._host, + args[1], + ) + + +def test_lookup_environment_history_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.lookup_environment_history( + environment.LookupEnvironmentHistoryRequest(), + name="name_value", + ) + + +def test_lookup_environment_history_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + environment.Environment(), + environment.Environment(), + ], + next_page_token="abc", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[], + next_page_token="def", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + ], + next_page_token="ghi", + ), + environment.LookupEnvironmentHistoryResponse( + environments=[ + environment.Environment(), + environment.Environment(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.LookupEnvironmentHistoryResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.lookup_environment_history(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.Environment) for i in results) + + pages = list(client.lookup_environment_history(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + environment.RunContinuousTestRequest, + dict, + ], +) +def test_run_continuous_test_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.run_continuous_test(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_run_continuous_test_rest_required_fields( + request_type=environment.RunContinuousTestRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["environment"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_continuous_test._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["environment"] = "environment_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_continuous_test._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "environment" in jsonified_request + assert jsonified_request["environment"] == "environment_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.run_continuous_test(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_run_continuous_test_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.run_continuous_test._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("environment",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_run_continuous_test_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_run_continuous_test" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_run_continuous_test" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.RunContinuousTestRequest.pb( + environment.RunContinuousTestRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = environment.RunContinuousTestRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.run_continuous_test( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_run_continuous_test_rest_bad_request( + transport: str = "rest", request_type=environment.RunContinuousTestRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.run_continuous_test(request) + + +def test_run_continuous_test_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + environment.ListContinuousTestResultsRequest, + dict, + ], +) +def test_list_continuous_test_results_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListContinuousTestResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_continuous_test_results(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListContinuousTestResultsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_continuous_test_results_rest_required_fields( + request_type=environment.ListContinuousTestResultsRequest, +): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_continuous_test_results._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_continuous_test_results._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = environment.ListContinuousTestResultsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_continuous_test_results(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_continuous_test_results_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_continuous_test_results._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_continuous_test_results_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_list_continuous_test_results" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_list_continuous_test_results" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.ListContinuousTestResultsRequest.pb( + environment.ListContinuousTestResultsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + environment.ListContinuousTestResultsResponse.to_json( + environment.ListContinuousTestResultsResponse() + ) + ) + + request = environment.ListContinuousTestResultsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = environment.ListContinuousTestResultsResponse() + + client.list_continuous_test_results( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_continuous_test_results_rest_bad_request( + transport: str = "rest", request_type=environment.ListContinuousTestResultsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_continuous_test_results(request) + + +def test_list_continuous_test_results_rest_flattened(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = environment.ListContinuousTestResultsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = environment.ListContinuousTestResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_continuous_test_results(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/continuousTestResults" + % client.transport._host, + args[1], + ) + + +def test_list_continuous_test_results_rest_flattened_error(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_continuous_test_results( + environment.ListContinuousTestResultsRequest(), + parent="parent_value", + ) + + +def test_list_continuous_test_results_rest_pager(transport: str = "rest"): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + ], + next_page_token="abc", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[], + next_page_token="def", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + ], + next_page_token="ghi", + ), + environment.ListContinuousTestResultsResponse( + continuous_test_results=[ + environment.ContinuousTestResult(), + environment.ContinuousTestResult(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + environment.ListContinuousTestResultsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + pager = client.list_continuous_test_results(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, environment.ContinuousTestResult) for i in results) + + pages = list(client.list_continuous_test_results(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + environment.DeployFlowRequest, + dict, + ], +) +def test_deploy_flow_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.deploy_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_deploy_flow_rest_required_fields(request_type=environment.DeployFlowRequest): + transport_class = transports.EnvironmentsRestTransport + + request_init = {} + request_init["environment"] = "" + request_init["flow_version"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).deploy_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["environment"] = "environment_value" + jsonified_request["flowVersion"] = "flow_version_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).deploy_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "environment" in jsonified_request + assert jsonified_request["environment"] == "environment_value" + assert "flowVersion" in jsonified_request + assert jsonified_request["flowVersion"] == "flow_version_value" + + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.deploy_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_deploy_flow_rest_unset_required_fields(): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.deploy_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "environment", + "flowVersion", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_deploy_flow_rest_interceptors(null_interceptor): + transport = transports.EnvironmentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.EnvironmentsRestInterceptor(), + ) + client = EnvironmentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.EnvironmentsRestInterceptor, "post_deploy_flow" + ) as post, mock.patch.object( + transports.EnvironmentsRestInterceptor, "pre_deploy_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = environment.DeployFlowRequest.pb(environment.DeployFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = environment.DeployFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.deploy_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_deploy_flow_rest_bad_request( + transport: str = "rest", request_type=environment.DeployFlowRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "environment": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.deploy_flow(request) + + +def test_deploy_flow_rest_error(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EnvironmentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = EnvironmentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.EnvironmentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.EnvironmentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.EnvironmentsGrpcTransport, + transports.EnvironmentsGrpcAsyncIOTransport, + transports.EnvironmentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = EnvironmentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.EnvironmentsGrpcTransport, + ) + + +def test_environments_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.EnvironmentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_environments_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.environments.transports.EnvironmentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.EnvironmentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_environments", @@ -3537,6 +6279,7 @@ def test_environments_transport_auth_adc(transport_class): [ transports.EnvironmentsGrpcTransport, transports.EnvironmentsGrpcAsyncIOTransport, + transports.EnvironmentsRestTransport, ], ) def test_environments_transport_auth_gdch_credentials(transport_class): @@ -3634,11 +6377,40 @@ def test_environments_grpc_transport_client_cert_source_for_mtls(transport_class ) +def test_environments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.EnvironmentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_environments_rest_lro_client(): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_environments_host_no_port(transport_name): @@ -3649,7 +6421,11 @@ def test_environments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3657,6 +6433,7 @@ def test_environments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_environments_host_with_port(transport_name): @@ -3667,7 +6444,57 @@ def test_environments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_environments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = EnvironmentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = EnvironmentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_environments._session + session2 = client2.transport.list_environments._session + assert session1 != session2 + session1 = client1.transport.get_environment._session + session2 = client2.transport.get_environment._session + assert session1 != session2 + session1 = client1.transport.create_environment._session + session2 = client2.transport.create_environment._session + assert session1 != session2 + session1 = client1.transport.update_environment._session + session2 = client2.transport.update_environment._session + assert session1 != session2 + session1 = client1.transport.delete_environment._session + session2 = client2.transport.delete_environment._session + assert session1 != session2 + session1 = client1.transport.lookup_environment_history._session + session2 = client2.transport.lookup_environment_history._session + assert session1 != session2 + session1 = client1.transport.run_continuous_test._session + session2 = client2.transport.run_continuous_test._session + assert session1 != session2 + session1 = client1.transport.list_continuous_test_results._session + session2 = client2.transport.list_continuous_test_results._session + assert session1 != session2 + session1 = client1.transport.deploy_flow._session + session2 = client2.transport.deploy_flow._session + assert session1 != session2 def test_environments_grpc_transport_channel(): @@ -4178,6 +7005,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = EnvironmentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = EnvironmentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4895,6 +8008,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4912,6 +8026,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_experiments.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_experiments.py index 80d925d1..2fbc1e15 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_experiments.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_experiments.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -100,6 +107,7 @@ def test__get_default_mtls_endpoint(): [ (ExperimentsClient, "grpc"), (ExperimentsAsyncClient, "grpc_asyncio"), + (ExperimentsClient, "rest"), ], ) def test_experiments_client_from_service_account_info(client_class, transport_name): @@ -113,7 +121,11 @@ def test_experiments_client_from_service_account_info(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -121,6 +133,7 @@ def test_experiments_client_from_service_account_info(client_class, transport_na [ (transports.ExperimentsGrpcTransport, "grpc"), (transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ExperimentsRestTransport, "rest"), ], ) def test_experiments_client_service_account_always_use_jwt( @@ -146,6 +159,7 @@ def test_experiments_client_service_account_always_use_jwt( [ (ExperimentsClient, "grpc"), (ExperimentsAsyncClient, "grpc_asyncio"), + (ExperimentsClient, "rest"), ], ) def test_experiments_client_from_service_account_file(client_class, transport_name): @@ -166,13 +180,18 @@ def test_experiments_client_from_service_account_file(client_class, transport_na assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_experiments_client_get_transport_class(): transport = ExperimentsClient.get_transport_class() available_transports = [ transports.ExperimentsGrpcTransport, + transports.ExperimentsRestTransport, ] assert transport in available_transports @@ -189,6 +208,7 @@ def test_experiments_client_get_transport_class(): transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -332,6 +352,8 @@ def test_experiments_client_client_options( "grpc_asyncio", "false", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", "true"), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -525,6 +547,7 @@ def test_experiments_client_get_mtls_endpoint_and_cert_source(client_class): transports.ExperimentsGrpcAsyncIOTransport, "grpc_asyncio", ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest"), ], ) def test_experiments_client_client_options_scopes( @@ -560,6 +583,7 @@ def test_experiments_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (ExperimentsClient, transports.ExperimentsRestTransport, "rest", None), ], ) def test_experiments_client_client_options_credentials_file( @@ -2612,209 +2636,2511 @@ async def test_stop_experiment_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.ExperimentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + experiment.ListExperimentsRequest, + dict, + ], +) +def test_list_experiments_rest(request_type): + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = ExperimentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_experiments(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListExperimentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_experiments_rest_required_fields( + request_type=experiment.ListExperimentsRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = ExperimentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_experiments._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_experiments._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.ExperimentsGrpcTransport( + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = ExperimentsClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.ExperimentsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_experiments(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_experiments_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.ExperimentsGrpcAsyncIOTransport( + unset_fields = transport.list_experiments._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_experiments_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_list_experiments" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_list_experiments" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.ListExperimentsRequest.pb( + experiment.ListExperimentsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.ListExperimentsResponse.to_json( + experiment.ListExperimentsResponse() + ) + request = experiment.ListExperimentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.ListExperimentsResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.ExperimentsGrpcTransport, - transports.ExperimentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_experiments( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = ExperimentsClient.get_transport_class(transport_name)( + +def test_list_experiments_rest_bad_request( + transport: str = "rest", request_type=experiment.ListExperimentsRequest +): + client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_experiments(request) + + +def test_list_experiments_rest_flattened(): client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.ExperimentsGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.ListExperimentsResponse() -def test_experiments_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.ExperimentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) + mock_args.update(sample_request) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.ListExperimentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -def test_experiments_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.ExperimentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_experiments(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/experiments" + % client.transport._host, + args[1], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_experiments", - "get_experiment", - "create_experiment", - "update_experiment", - "delete_experiment", - "start_experiment", - "stop_experiment", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + +def test_list_experiments_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_experiments( + experiment.ListExperimentsRequest(), + parent="parent_value", + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_experiments_rest_pager(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_experiments_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.ExperimentsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + experiment.Experiment(), + experiment.Experiment(), + ], + next_page_token="abc", + ), + experiment.ListExperimentsResponse( + experiments=[], + next_page_token="def", + ), + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + ], + next_page_token="ghi", + ), + experiment.ListExperimentsResponse( + experiments=[ + experiment.Experiment(), + experiment.Experiment(), + ], ), - quota_project_id="octopus", ) + # Two responses for two calls + response = response + response + # Wrap the values into proper Response objs + response = tuple( + experiment.ListExperimentsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -def test_experiments_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.ExperimentsTransport() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + pager = client.list_experiments(request=sample_request) -def test_experiments_auth_adc(): - # If no credentials are provided, we should use ADC credentials. + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, experiment.Experiment) for i in results) + + pages = list(client.list_experiments(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.GetExperimentRequest, + dict, + ], +) +def test_get_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_get_experiment_rest_required_fields( + request_type=experiment.GetExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_get_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_get_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.GetExperimentRequest.pb( + experiment.GetExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.GetExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.get_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.GetExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_experiment(request) + + +def test_get_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_get_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_experiment( + experiment.GetExperimentRequest(), + name="name_value", + ) + + +def test_get_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_experiment.CreateExperimentRequest, + dict, + ], +) +def test_create_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request_init["experiment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_create_experiment_rest_required_fields( + request_type=gcdc_experiment.CreateExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_experiment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "experiment", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_create_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_create_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_experiment.CreateExperimentRequest.pb( + gcdc_experiment.CreateExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_experiment.Experiment.to_json( + gcdc_experiment.Experiment() + ) + + request = gcdc_experiment.CreateExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_experiment.Experiment() + + client.create_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_experiment_rest_bad_request( + transport: str = "rest", request_type=gcdc_experiment.CreateExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + request_init["experiment"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_experiment(request) + + +def test_create_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/environments/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + experiment=gcdc_experiment.Experiment(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/environments/*}/experiments" + % client.transport._host, + args[1], + ) + + +def test_create_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_experiment( + gcdc_experiment.CreateExperimentRequest(), + parent="parent_value", + experiment=gcdc_experiment.Experiment(name="name_value"), + ) + + +def test_create_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_experiment.UpdateExperimentRequest, + dict, + ], +) +def test_update_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + request_init["experiment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_update_experiment_rest_required_fields( + request_type=gcdc_experiment.UpdateExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_experiment._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_experiment._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "experiment", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_update_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_update_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_experiment.UpdateExperimentRequest.pb( + gcdc_experiment.UpdateExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_experiment.Experiment.to_json( + gcdc_experiment.Experiment() + ) + + request = gcdc_experiment.UpdateExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_experiment.Experiment() + + client.update_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_experiment_rest_bad_request( + transport: str = "rest", request_type=gcdc_experiment.UpdateExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + request_init["experiment"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5", + "display_name": "display_name_value", + "description": "description_value", + "state": 1, + "definition": { + "condition": "condition_value", + "version_variants": { + "variants": [ + { + "version": "version_value", + "traffic_allocation": 0.1892, + "is_control_group": True, + } + ] + }, + }, + "rollout_config": { + "rollout_steps": [ + { + "display_name": "display_name_value", + "traffic_percent": 1583, + "min_duration": {"seconds": 751, "nanos": 543}, + } + ], + "rollout_condition": "rollout_condition_value", + "failure_condition": "failure_condition_value", + }, + "rollout_state": { + "step": "step_value", + "step_index": 1075, + "start_time": {"seconds": 751, "nanos": 543}, + }, + "rollout_failure_reason": "rollout_failure_reason_value", + "result": { + "version_metrics": [ + { + "version": "version_value", + "metrics": [ + { + "type_": 1, + "count_type": 1, + "ratio": 0.543, + "count": 0.553, + "confidence_interval": { + "confidence_level": 0.16690000000000002, + "ratio": 0.543, + "lower_bound": 0.1184, + "upper_bound": 0.1187, + }, + } + ], + "session_count": 1420, + } + ], + "last_update_time": {}, + }, + "create_time": {}, + "start_time": {}, + "end_time": {}, + "last_update_time": {}, + "experiment_length": {}, + "variants_history": [{"version_variants": {}, "update_time": {}}], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_experiment(request) + + +def test_update_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "experiment": { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + experiment=gcdc_experiment.Experiment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{experiment.name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_update_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_experiment( + gcdc_experiment.UpdateExperimentRequest(), + experiment=gcdc_experiment.Experiment(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.DeleteExperimentRequest, + dict, + ], +) +def test_delete_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_experiment(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_experiment_rest_required_fields( + request_type=experiment.DeleteExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_delete_experiment" + ) as pre: + pre.assert_not_called() + pb_message = experiment.DeleteExperimentRequest.pb( + experiment.DeleteExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = experiment.DeleteExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.DeleteExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_experiment(request) + + +def test_delete_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_experiment( + experiment.DeleteExperimentRequest(), + name="name_value", + ) + + +def test_delete_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.StartExperimentRequest, + dict, + ], +) +def test_start_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.start_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_start_experiment_rest_required_fields( + request_type=experiment.StartExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).start_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).start_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.start_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_start_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.start_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_start_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_start_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_start_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.StartExperimentRequest.pb( + experiment.StartExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.StartExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.start_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_start_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.StartExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.start_experiment(request) + + +def test_start_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.start_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:start" + % client.transport._host, + args[1], + ) + + +def test_start_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.start_experiment( + experiment.StartExperimentRequest(), + name="name_value", + ) + + +def test_start_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + experiment.StopExperimentRequest, + dict, + ], +) +def test_stop_experiment_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment( + name="name_value", + display_name="display_name_value", + description="description_value", + state=experiment.Experiment.State.DRAFT, + rollout_failure_reason="rollout_failure_reason_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.stop_experiment(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, experiment.Experiment) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == experiment.Experiment.State.DRAFT + assert response.rollout_failure_reason == "rollout_failure_reason_value" + + +def test_stop_experiment_rest_required_fields( + request_type=experiment.StopExperimentRequest, +): + transport_class = transports.ExperimentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).stop_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).stop_experiment._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.stop_experiment(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_stop_experiment_rest_unset_required_fields(): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.stop_experiment._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_stop_experiment_rest_interceptors(null_interceptor): + transport = transports.ExperimentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ExperimentsRestInterceptor(), + ) + client = ExperimentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ExperimentsRestInterceptor, "post_stop_experiment" + ) as post, mock.patch.object( + transports.ExperimentsRestInterceptor, "pre_stop_experiment" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = experiment.StopExperimentRequest.pb( + experiment.StopExperimentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = experiment.Experiment.to_json( + experiment.Experiment() + ) + + request = experiment.StopExperimentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = experiment.Experiment() + + client.stop_experiment( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_stop_experiment_rest_bad_request( + transport: str = "rest", request_type=experiment.StopExperimentRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.stop_experiment(request) + + +def test_stop_experiment_rest_flattened(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = experiment.Experiment() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/environments/sample4/experiments/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = experiment.Experiment.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.stop_experiment(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/environments/*/experiments/*}:stop" + % client.transport._host, + args[1], + ) + + +def test_stop_experiment_rest_flattened_error(transport: str = "rest"): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.stop_experiment( + experiment.StopExperimentRequest(), + name="name_value", + ) + + +def test_stop_experiment_rest_error(): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ExperimentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = ExperimentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.ExperimentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.ExperimentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ExperimentsGrpcTransport, + transports.ExperimentsGrpcAsyncIOTransport, + transports.ExperimentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = ExperimentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.ExperimentsGrpcTransport, + ) + + +def test_experiments_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.ExperimentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_experiments_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.ExperimentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_experiments", + "get_experiment", + "create_experiment", + "update_experiment", + "delete_experiment", + "start_experiment", + "stop_experiment", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_experiments_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ExperimentsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_experiments_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.experiments.transports.ExperimentsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ExperimentsTransport() + adc.assert_called_once() + + +def test_experiments_auth_adc(): + # If no credentials are provided, we should use ADC credentials. with mock.patch.object(google.auth, "default", autospec=True) as adc: adc.return_value = (ga_credentials.AnonymousCredentials(), None) ExperimentsClient() @@ -2856,6 +5182,7 @@ def test_experiments_transport_auth_adc(transport_class): [ transports.ExperimentsGrpcTransport, transports.ExperimentsGrpcAsyncIOTransport, + transports.ExperimentsRestTransport, ], ) def test_experiments_transport_auth_gdch_credentials(transport_class): @@ -2953,11 +5280,23 @@ def test_experiments_grpc_transport_client_cert_source_for_mtls(transport_class) ) +def test_experiments_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ExperimentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_experiments_host_no_port(transport_name): @@ -2968,7 +5307,11 @@ def test_experiments_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2976,6 +5319,7 @@ def test_experiments_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_experiments_host_with_port(transport_name): @@ -2986,7 +5330,51 @@ def test_experiments_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_experiments_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ExperimentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ExperimentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_experiments._session + session2 = client2.transport.list_experiments._session + assert session1 != session2 + session1 = client1.transport.get_experiment._session + session2 = client2.transport.get_experiment._session + assert session1 != session2 + session1 = client1.transport.create_experiment._session + session2 = client2.transport.create_experiment._session + assert session1 != session2 + session1 = client1.transport.update_experiment._session + session2 = client2.transport.update_experiment._session + assert session1 != session2 + session1 = client1.transport.delete_experiment._session + session2 = client2.transport.delete_experiment._session + assert session1 != session2 + session1 = client1.transport.start_experiment._session + session2 = client2.transport.start_experiment._session + assert session1 != session2 + session1 = client1.transport.stop_experiment._session + session2 = client2.transport.stop_experiment._session + assert session1 != session2 def test_experiments_grpc_transport_channel(): @@ -3313,6 +5701,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = ExperimentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = ExperimentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4030,6 +6704,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4047,6 +6722,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_flows.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_flows.py index a5b0de3f..f1fe3715 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_flows.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_flows.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -104,6 +111,7 @@ def test__get_default_mtls_endpoint(): [ (FlowsClient, "grpc"), (FlowsAsyncClient, "grpc_asyncio"), + (FlowsClient, "rest"), ], ) def test_flows_client_from_service_account_info(client_class, transport_name): @@ -117,7 +125,11 @@ def test_flows_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -125,6 +137,7 @@ def test_flows_client_from_service_account_info(client_class, transport_name): [ (transports.FlowsGrpcTransport, "grpc"), (transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.FlowsRestTransport, "rest"), ], ) def test_flows_client_service_account_always_use_jwt(transport_class, transport_name): @@ -148,6 +161,7 @@ def test_flows_client_service_account_always_use_jwt(transport_class, transport_ [ (FlowsClient, "grpc"), (FlowsAsyncClient, "grpc_asyncio"), + (FlowsClient, "rest"), ], ) def test_flows_client_from_service_account_file(client_class, transport_name): @@ -168,13 +182,18 @@ def test_flows_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_flows_client_get_transport_class(): transport = FlowsClient.get_transport_class() available_transports = [ transports.FlowsGrpcTransport, + transports.FlowsRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_flows_client_get_transport_class(): [ (FlowsClient, transports.FlowsGrpcTransport, "grpc"), (FlowsAsyncClient, transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (FlowsClient, transports.FlowsRestTransport, "rest"), ], ) @mock.patch.object( @@ -326,6 +346,8 @@ def test_flows_client_client_options(client_class, transport_class, transport_na "grpc_asyncio", "false", ), + (FlowsClient, transports.FlowsRestTransport, "rest", "true"), + (FlowsClient, transports.FlowsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -511,6 +533,7 @@ def test_flows_client_get_mtls_endpoint_and_cert_source(client_class): [ (FlowsClient, transports.FlowsGrpcTransport, "grpc"), (FlowsAsyncClient, transports.FlowsGrpcAsyncIOTransport, "grpc_asyncio"), + (FlowsClient, transports.FlowsRestTransport, "rest"), ], ) def test_flows_client_client_options_scopes( @@ -546,6 +569,7 @@ def test_flows_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (FlowsClient, transports.FlowsRestTransport, "rest", None), ], ) def test_flows_client_client_options_credentials_file( @@ -2944,209 +2968,3187 @@ async def test_export_flow_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.FlowsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + gcdc_flow.CreateFlowRequest, + dict, + ], +) +def test_create_flow_rest(request_type): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = FlowsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["flow"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.FlowsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = FlowsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_create_flow_rest_required_fields(request_type=gcdc_flow.CreateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.FlowsGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = FlowsClient( - client_options=options, - transport=transport, - ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = FlowsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + unset_fields = transport.create_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "flow", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.FlowsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), ) - with pytest.raises(ValueError): - client = FlowsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_create_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_create_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_flow.CreateFlowRequest.pb(gcdc_flow.CreateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_flow.Flow.to_json(gcdc_flow.Flow()) + + request = gcdc_flow.CreateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_flow.Flow() + + client.create_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.FlowsGrpcTransport( + +def test_create_flow_rest_bad_request( + transport: str = "rest", request_type=gcdc_flow.CreateFlowRequest +): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = FlowsClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["flow"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.FlowsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_flow(request) + + +def test_create_flow_rest_flattened(): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.FlowsGrpcAsyncIOTransport( + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + flow=gcdc_flow.Flow(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/flows" + % client.transport._host, + args[1], + ) + + +def test_create_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_flow( + gcdc_flow.CreateFlowRequest(), + parent="parent_value", + flow=gcdc_flow.Flow(name="name_value"), + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.FlowsGrpcTransport, - transports.FlowsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + +def test_create_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + flow.DeleteFlowRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = FlowsClient.get_transport_class(transport_name)( +def test_delete_flow_rest(request_type): + client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = FlowsClient( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.FlowsGrpcTransport, - ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" -def test_flows_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.FlowsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_flow(request) + # Establish that the response is the type that we expect. + assert response is None -def test_flows_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.FlowsTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "create_flow", - "delete_flow", - "list_flows", - "get_flow", - "update_flow", - "train_flow", - "validate_flow", - "get_flow_validation_result", - "import_flow", - "export_flow", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", +def test_delete_flow_rest_required_fields(request_type=flow.DeleteFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Additionally, the LRO client (a property) should - # also raise NotImplementedError - with pytest.raises(NotImplementedError): - transport.operations_client + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # verify required fields with default values are now present + jsonified_request["name"] = "name_value" -def test_flows_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.FlowsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", - ) + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_flows_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport._prep_wrapped_messages" - ) as Transport: + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "pre_delete_flow" + ) as pre: + pre.assert_not_called() + pb_message = flow.DeleteFlowRequest.pb(flow.DeleteFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = flow.DeleteFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_flow_rest_bad_request( + transport: str = "rest", request_type=flow.DeleteFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_flow(request) + + +def test_delete_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_flow( + flow.DeleteFlowRequest(), + name="name_value", + ) + + +def test_delete_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ListFlowsRequest, + dict, + ], +) +def test_list_flows_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_flows(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListFlowsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_flows_rest_required_fields(request_type=flow.ListFlowsRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_flows._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_flows._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_flows(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_flows_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_flows._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_flows_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_list_flows" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_list_flows" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ListFlowsRequest.pb(flow.ListFlowsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.ListFlowsResponse.to_json( + flow.ListFlowsResponse() + ) + + request = flow.ListFlowsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.ListFlowsResponse() + + client.list_flows( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_flows_rest_bad_request( + transport: str = "rest", request_type=flow.ListFlowsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_flows(request) + + +def test_list_flows_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.ListFlowsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.ListFlowsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_flows(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/flows" + % client.transport._host, + args[1], + ) + + +def test_list_flows_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_flows( + flow.ListFlowsRequest(), + parent="parent_value", + ) + + +def test_list_flows_rest_pager(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + flow.Flow(), + flow.Flow(), + ], + next_page_token="abc", + ), + flow.ListFlowsResponse( + flows=[], + next_page_token="def", + ), + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + ], + next_page_token="ghi", + ), + flow.ListFlowsResponse( + flows=[ + flow.Flow(), + flow.Flow(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(flow.ListFlowsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_flows(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, flow.Flow) for i in results) + + pages = list(client.list_flows(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + flow.GetFlowRequest, + dict, + ], +) +def test_get_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_get_flow_rest_required_fields(request_type=flow.GetFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_get_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_get_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.GetFlowRequest.pb(flow.GetFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.Flow.to_json(flow.Flow()) + + request = flow.GetFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.Flow() + + client.get_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_flow_rest_bad_request( + transport: str = "rest", request_type=flow.GetFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_flow(request) + + +def test_get_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_get_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_flow( + flow.GetFlowRequest(), + name="name_value", + ) + + +def test_get_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_flow.UpdateFlowRequest, + dict, + ], +) +def test_update_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + request_init["flow"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow( + name="name_value", + display_name="display_name_value", + description="description_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_flow.Flow) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_update_flow_rest_required_fields(request_type=gcdc_flow.UpdateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_flow._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_flow._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("flow",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_update_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_update_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_flow.UpdateFlowRequest.pb(gcdc_flow.UpdateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_flow.Flow.to_json(gcdc_flow.Flow()) + + request = gcdc_flow.UpdateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_flow.Flow() + + client.update_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_flow_rest_bad_request( + transport: str = "rest", request_type=gcdc_flow.UpdateFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + request_init["flow"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4", + "display_name": "display_name_value", + "description": "description_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_flow(request) + + +def test_update_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_flow.Flow() + + # get arguments that satisfy an http rule for this method + sample_request = { + "flow": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + flow=gcdc_flow.Flow(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_flow.Flow.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{flow.name=projects/*/locations/*/agents/*/flows/*}" + % client.transport._host, + args[1], + ) + + +def test_update_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_flow( + gcdc_flow.UpdateFlowRequest(), + flow=gcdc_flow.Flow(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.TrainFlowRequest, + dict, + ], +) +def test_train_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.train_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_train_flow_rest_required_fields(request_type=flow.TrainFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).train_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).train_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.train_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_train_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.train_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_train_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_train_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_train_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.TrainFlowRequest.pb(flow.TrainFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.TrainFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.train_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_train_flow_rest_bad_request( + transport: str = "rest", request_type=flow.TrainFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.train_flow(request) + + +def test_train_flow_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.train_flow(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*}:train" + % client.transport._host, + args[1], + ) + + +def test_train_flow_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.train_flow( + flow.TrainFlowRequest(), + name="name_value", + ) + + +def test_train_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ValidateFlowRequest, + dict, + ], +) +def test_validate_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_flow(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.FlowValidationResult) + assert response.name == "name_value" + + +def test_validate_flow_rest_required_fields(request_type=flow.ValidateFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_validate_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_validate_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ValidateFlowRequest.pb(flow.ValidateFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.FlowValidationResult.to_json( + flow.FlowValidationResult() + ) + + request = flow.ValidateFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.FlowValidationResult() + + client.validate_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ValidateFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_flow(request) + + +def test_validate_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.GetFlowValidationResultRequest, + dict, + ], +) +def test_get_flow_validation_result_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult( + name="name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_flow_validation_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, flow.FlowValidationResult) + assert response.name == "name_value" + + +def test_get_flow_validation_result_rest_required_fields( + request_type=flow.GetFlowValidationResultRequest, +): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow_validation_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_flow_validation_result._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_flow_validation_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_flow_validation_result_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_flow_validation_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_flow_validation_result_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.FlowsRestInterceptor, "post_get_flow_validation_result" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_get_flow_validation_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.GetFlowValidationResultRequest.pb( + flow.GetFlowValidationResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = flow.FlowValidationResult.to_json( + flow.FlowValidationResult() + ) + + request = flow.GetFlowValidationResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = flow.FlowValidationResult() + + client.get_flow_validation_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_flow_validation_result_rest_bad_request( + transport: str = "rest", request_type=flow.GetFlowValidationResultRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_flow_validation_result(request) + + +def test_get_flow_validation_result_rest_flattened(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = flow.FlowValidationResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/validationResult" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = flow.FlowValidationResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_flow_validation_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/validationResult}" + % client.transport._host, + args[1], + ) + + +def test_get_flow_validation_result_rest_flattened_error(transport: str = "rest"): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_flow_validation_result( + flow.GetFlowValidationResultRequest(), + name="name_value", + ) + + +def test_get_flow_validation_result_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ImportFlowRequest, + dict, + ], +) +def test_import_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.import_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_import_flow_rest_required_fields(request_type=flow.ImportFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.import_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_import_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.import_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_import_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_import_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_import_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ImportFlowRequest.pb(flow.ImportFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.ImportFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.import_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_import_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ImportFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.import_flow(request) + + +def test_import_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + flow.ExportFlowRequest, + dict, + ], +) +def test_export_flow_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_flow(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_flow_rest_required_fields(request_type=flow.ExportFlowRequest): + transport_class = transports.FlowsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_flow._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_flow(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_flow_rest_unset_required_fields(): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_flow._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_flow_rest_interceptors(null_interceptor): + transport = transports.FlowsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.FlowsRestInterceptor(), + ) + client = FlowsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.FlowsRestInterceptor, "post_export_flow" + ) as post, mock.patch.object( + transports.FlowsRestInterceptor, "pre_export_flow" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = flow.ExportFlowRequest.pb(flow.ExportFlowRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = flow.ExportFlowRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_flow( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_flow_rest_bad_request( + transport: str = "rest", request_type=flow.ExportFlowRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_flow(request) + + +def test_export_flow_rest_error(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = FlowsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = FlowsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = FlowsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = FlowsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.FlowsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.FlowsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.FlowsGrpcTransport, + transports.FlowsGrpcAsyncIOTransport, + transports.FlowsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = FlowsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.FlowsGrpcTransport, + ) + + +def test_flows_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.FlowsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_flows_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.FlowsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_flow", + "delete_flow", + "list_flows", + "get_flow", + "update_flow", + "train_flow", + "validate_flow", + "get_flow_validation_result", + "import_flow", + "export_flow", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Additionally, the LRO client (a property) should + # also raise NotImplementedError + with pytest.raises(NotImplementedError): + transport.operations_client + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_flows_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.FlowsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_flows_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.flows.transports.FlowsTransport._prep_wrapped_messages" + ) as Transport: Transport.return_value = None adc.return_value = (ga_credentials.AnonymousCredentials(), None) transport = transports.FlowsTransport() @@ -3196,6 +6198,7 @@ def test_flows_transport_auth_adc(transport_class): [ transports.FlowsGrpcTransport, transports.FlowsGrpcAsyncIOTransport, + transports.FlowsRestTransport, ], ) def test_flows_transport_auth_gdch_credentials(transport_class): @@ -3293,11 +6296,40 @@ def test_flows_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_flows_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.FlowsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_flows_rest_lro_client(): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_flows_host_no_port(transport_name): @@ -3308,7 +6340,11 @@ def test_flows_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3316,6 +6352,7 @@ def test_flows_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_flows_host_with_port(transport_name): @@ -3326,7 +6363,60 @@ def test_flows_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_flows_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = FlowsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = FlowsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_flow._session + session2 = client2.transport.create_flow._session + assert session1 != session2 + session1 = client1.transport.delete_flow._session + session2 = client2.transport.delete_flow._session + assert session1 != session2 + session1 = client1.transport.list_flows._session + session2 = client2.transport.list_flows._session + assert session1 != session2 + session1 = client1.transport.get_flow._session + session2 = client2.transport.get_flow._session + assert session1 != session2 + session1 = client1.transport.update_flow._session + session2 = client2.transport.update_flow._session + assert session1 != session2 + session1 = client1.transport.train_flow._session + session2 = client2.transport.train_flow._session + assert session1 != session2 + session1 = client1.transport.validate_flow._session + session2 = client2.transport.validate_flow._session + assert session1 != session2 + session1 = client1.transport.get_flow_validation_result._session + session2 = client2.transport.get_flow_validation_result._session + assert session1 != session2 + session1 = client1.transport.import_flow._session + session2 = client2.transport.import_flow._session + assert session1 != session2 + session1 = client1.transport.export_flow._session + session2 = client2.transport.export_flow._session + assert session1 != session2 def test_flows_grpc_transport_channel(): @@ -3801,6 +6891,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = FlowsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = FlowsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4518,6 +7894,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4535,6 +7912,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_intents.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_intents.py index c277e413..4dac57be 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_intents.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_intents.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -93,6 +100,7 @@ def test__get_default_mtls_endpoint(): [ (IntentsClient, "grpc"), (IntentsAsyncClient, "grpc_asyncio"), + (IntentsClient, "rest"), ], ) def test_intents_client_from_service_account_info(client_class, transport_name): @@ -106,7 +114,11 @@ def test_intents_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -114,6 +126,7 @@ def test_intents_client_from_service_account_info(client_class, transport_name): [ (transports.IntentsGrpcTransport, "grpc"), (transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.IntentsRestTransport, "rest"), ], ) def test_intents_client_service_account_always_use_jwt(transport_class, transport_name): @@ -137,6 +150,7 @@ def test_intents_client_service_account_always_use_jwt(transport_class, transpor [ (IntentsClient, "grpc"), (IntentsAsyncClient, "grpc_asyncio"), + (IntentsClient, "rest"), ], ) def test_intents_client_from_service_account_file(client_class, transport_name): @@ -157,13 +171,18 @@ def test_intents_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_intents_client_get_transport_class(): transport = IntentsClient.get_transport_class() available_transports = [ transports.IntentsGrpcTransport, + transports.IntentsRestTransport, ] assert transport in available_transports @@ -176,6 +195,7 @@ def test_intents_client_get_transport_class(): [ (IntentsClient, transports.IntentsGrpcTransport, "grpc"), (IntentsAsyncClient, transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (IntentsClient, transports.IntentsRestTransport, "rest"), ], ) @mock.patch.object( @@ -315,6 +335,8 @@ def test_intents_client_client_options(client_class, transport_class, transport_ "grpc_asyncio", "false", ), + (IntentsClient, transports.IntentsRestTransport, "rest", "true"), + (IntentsClient, transports.IntentsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -500,6 +522,7 @@ def test_intents_client_get_mtls_endpoint_and_cert_source(client_class): [ (IntentsClient, transports.IntentsGrpcTransport, "grpc"), (IntentsAsyncClient, transports.IntentsGrpcAsyncIOTransport, "grpc_asyncio"), + (IntentsClient, transports.IntentsRestTransport, "rest"), ], ) def test_intents_client_client_options_scopes( @@ -535,6 +558,7 @@ def test_intents_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (IntentsClient, transports.IntentsRestTransport, "rest", None), ], ) def test_intents_client_client_options_credentials_file( @@ -2035,141 +2059,1682 @@ async def test_delete_intent_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.IntentsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + intent.ListIntentsRequest, + dict, + ], +) +def test_list_intents_rest(request_type): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = IntentsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.IntentsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_intents(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListIntentsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_intents_rest_required_fields(request_type=intent.ListIntentsRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_intents._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_intents._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "intent_view", + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = IntentsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_intents(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_intents_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_intents._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "intentView", + "languageCode", + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.IntentsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_intents_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = IntentsClient( - client_options=options, - transport=transport, + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_list_intents" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_list_intents" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = intent.ListIntentsRequest.pb(intent.ListIntentsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = intent.ListIntentsResponse.to_json( + intent.ListIntentsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = IntentsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = intent.ListIntentsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = intent.ListIntentsResponse() + + client.list_intents( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.IntentsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_intents_rest_bad_request( + transport: str = "rest", request_type=intent.ListIntentsRequest +): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = IntentsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.IntentsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_intents(request) + + +def test_list_intents_rest_flattened(): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = IntentsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.ListIntentsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.IntentsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.ListIntentsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_intents(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/intents" + % client.transport._host, + args[1], + ) + + +def test_list_intents_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.IntentsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_intents( + intent.ListIntentsRequest(), + parent="parent_value", + ) + + +def test_list_intents_rest_pager(transport: str = "rest"): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + intent.Intent(), + intent.Intent(), + ], + next_page_token="abc", + ), + intent.ListIntentsResponse( + intents=[], + next_page_token="def", + ), + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + ], + next_page_token="ghi", + ), + intent.ListIntentsResponse( + intents=[ + intent.Intent(), + intent.Intent(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(intent.ListIntentsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.IntentsGrpcTransport, - transports.IntentsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_intents(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, intent.Intent) for i in results) + + pages = list(client.list_intents(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + intent.GetIntentRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = IntentsClient.get_transport_class(transport_name)( +def test_get_intent_rest(request_type): + client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_get_intent_rest_required_fields(request_type=intent.GetIntentRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert isinstance( - client.transport, - transports.IntentsGrpcTransport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_intent._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) -def test_intents_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.IntentsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_get_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_get_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = intent.GetIntentRequest.pb(intent.GetIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = intent.Intent.to_json(intent.Intent()) + + request = intent.GetIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = intent.Intent() -def test_intents_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.intents.transports.IntentsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.IntentsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_intent_rest_bad_request( + transport: str = "rest", request_type=intent.GetIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_intent(request) + + +def test_get_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_get_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_intent( + intent.GetIntentRequest(), + name="name_value", + ) + + +def test_get_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_intent.CreateIntentRequest, + dict, + ], +) +def test_create_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["intent"] = { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_create_intent_rest_required_fields( + request_type=gcdc_intent.CreateIntentRequest, +): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "intent", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_create_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_create_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_intent.CreateIntentRequest.pb( + gcdc_intent.CreateIntentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_intent.Intent.to_json(gcdc_intent.Intent()) + + request = gcdc_intent.CreateIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_intent.Intent() + + client.create_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_intent_rest_bad_request( + transport: str = "rest", request_type=gcdc_intent.CreateIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["intent"] = { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_intent(request) + + +def test_create_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + intent=gcdc_intent.Intent(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/intents" + % client.transport._host, + args[1], + ) + + +def test_create_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_intent( + gcdc_intent.CreateIntentRequest(), + parent="parent_value", + intent=gcdc_intent.Intent(name="name_value"), + ) + + +def test_create_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_intent.UpdateIntentRequest, + dict, + ], +) +def test_update_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + request_init["intent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent( + name="name_value", + display_name="display_name_value", + priority=898, + is_fallback=True, + description="description_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_intent.Intent) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.priority == 898 + assert response.is_fallback is True + assert response.description == "description_value" + + +def test_update_intent_rest_required_fields( + request_type=gcdc_intent.UpdateIntentRequest, +): + transport_class = transports.IntentsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_intent._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("intent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "post_update_intent" + ) as post, mock.patch.object( + transports.IntentsRestInterceptor, "pre_update_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_intent.UpdateIntentRequest.pb( + gcdc_intent.UpdateIntentRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_intent.Intent.to_json(gcdc_intent.Intent()) + + request = gcdc_intent.UpdateIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_intent.Intent() + + client.update_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_intent_rest_bad_request( + transport: str = "rest", request_type=gcdc_intent.UpdateIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + request_init["intent"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [{"text": "text_value", "parameter_id": "parameter_id_value"}], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_intent(request) + + +def test_update_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_intent.Intent() + + # get arguments that satisfy an http rule for this method + sample_request = { + "intent": { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + intent=gcdc_intent.Intent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_intent.Intent.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{intent.name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_update_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_intent( + gcdc_intent.UpdateIntentRequest(), + intent=gcdc_intent.Intent(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + intent.DeleteIntentRequest, + dict, + ], +) +def test_delete_intent_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_intent(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_intent_rest_required_fields(request_type=intent.DeleteIntentRequest): + transport_class = transports.IntentsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_intent_rest_unset_required_fields(): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_intent._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_intent_rest_interceptors(null_interceptor): + transport = transports.IntentsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.IntentsRestInterceptor(), + ) + client = IntentsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.IntentsRestInterceptor, "pre_delete_intent" + ) as pre: + pre.assert_not_called() + pb_message = intent.DeleteIntentRequest.pb(intent.DeleteIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = intent.DeleteIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_intent_rest_bad_request( + transport: str = "rest", request_type=intent.DeleteIntentRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_intent(request) + + +def test_delete_intent_rest_flattened(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/intents/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_intent(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/intents/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_intent_rest_flattened_error(transport: str = "rest"): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_intent( + intent.DeleteIntentRequest(), + name="name_value", + ) + + +def test_delete_intent_rest_error(): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = IntentsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = IntentsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IntentsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = IntentsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.IntentsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.IntentsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IntentsGrpcTransport, + transports.IntentsGrpcAsyncIOTransport, + transports.IntentsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = IntentsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.IntentsGrpcTransport, + ) + + +def test_intents_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.IntentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_intents_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.intents.transports.IntentsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.IntentsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_intents", @@ -2277,6 +3842,7 @@ def test_intents_transport_auth_adc(transport_class): [ transports.IntentsGrpcTransport, transports.IntentsGrpcAsyncIOTransport, + transports.IntentsRestTransport, ], ) def test_intents_transport_auth_gdch_credentials(transport_class): @@ -2374,11 +3940,23 @@ def test_intents_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_intents_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.IntentsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_intents_host_no_port(transport_name): @@ -2389,7 +3967,11 @@ def test_intents_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2397,6 +3979,7 @@ def test_intents_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_intents_host_with_port(transport_name): @@ -2407,7 +3990,45 @@ def test_intents_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_intents_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = IntentsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = IntentsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_intents._session + session2 = client2.transport.list_intents._session + assert session1 != session2 + session1 = client1.transport.get_intent._session + session2 = client2.transport.get_intent._session + assert session1 != session2 + session1 = client1.transport.create_intent._session + session2 = client2.transport.create_intent._session + assert session1 != session2 + session1 = client1.transport.update_intent._session + session2 = client2.transport.update_intent._session + assert session1 != session2 + session1 = client1.transport.delete_intent._session + session2 = client2.transport.delete_intent._session + assert session1 != session2 def test_intents_grpc_transport_channel(): @@ -2726,6 +4347,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = IntentsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = IntentsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3443,6 +5350,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3460,6 +5368,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_pages.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_pages.py index 27b83665..f0dbc2ab 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_pages.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_pages.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -96,6 +103,7 @@ def test__get_default_mtls_endpoint(): [ (PagesClient, "grpc"), (PagesAsyncClient, "grpc_asyncio"), + (PagesClient, "rest"), ], ) def test_pages_client_from_service_account_info(client_class, transport_name): @@ -109,7 +117,11 @@ def test_pages_client_from_service_account_info(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -117,6 +129,7 @@ def test_pages_client_from_service_account_info(client_class, transport_name): [ (transports.PagesGrpcTransport, "grpc"), (transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.PagesRestTransport, "rest"), ], ) def test_pages_client_service_account_always_use_jwt(transport_class, transport_name): @@ -140,6 +153,7 @@ def test_pages_client_service_account_always_use_jwt(transport_class, transport_ [ (PagesClient, "grpc"), (PagesAsyncClient, "grpc_asyncio"), + (PagesClient, "rest"), ], ) def test_pages_client_from_service_account_file(client_class, transport_name): @@ -160,13 +174,18 @@ def test_pages_client_from_service_account_file(client_class, transport_name): assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_pages_client_get_transport_class(): transport = PagesClient.get_transport_class() available_transports = [ transports.PagesGrpcTransport, + transports.PagesRestTransport, ] assert transport in available_transports @@ -179,6 +198,7 @@ def test_pages_client_get_transport_class(): [ (PagesClient, transports.PagesGrpcTransport, "grpc"), (PagesAsyncClient, transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (PagesClient, transports.PagesRestTransport, "rest"), ], ) @mock.patch.object( @@ -318,6 +338,8 @@ def test_pages_client_client_options(client_class, transport_class, transport_na "grpc_asyncio", "false", ), + (PagesClient, transports.PagesRestTransport, "rest", "true"), + (PagesClient, transports.PagesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -503,6 +525,7 @@ def test_pages_client_get_mtls_endpoint_and_cert_source(client_class): [ (PagesClient, transports.PagesGrpcTransport, "grpc"), (PagesAsyncClient, transports.PagesGrpcAsyncIOTransport, "grpc_asyncio"), + (PagesClient, transports.PagesRestTransport, "rest"), ], ) def test_pages_client_client_options_scopes( @@ -538,6 +561,7 @@ def test_pages_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (PagesClient, transports.PagesRestTransport, "rest", None), ], ) def test_pages_client_client_options_credentials_file( @@ -2014,168 +2038,2023 @@ async def test_delete_page_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.PagesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + page.ListPagesRequest, + dict, + ], +) +def test_list_pages_rest(request_type): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = PagesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.PagesGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_pages(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListPagesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_pages_rest_required_fields(request_type=page.ListPagesRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_pages._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_pages._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = PagesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_pages(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_pages_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_pages._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.PagesGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_pages_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PagesClient( - client_options=options, - transport=transport, + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_list_pages" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_list_pages" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = page.ListPagesRequest.pb(page.ListPagesRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = page.ListPagesResponse.to_json( + page.ListPagesResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PagesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = page.ListPagesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = page.ListPagesResponse() + + client.list_pages( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.PagesGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_pages_rest_bad_request( + transport: str = "rest", request_type=page.ListPagesRequest +): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = PagesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.PagesGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_pages(request) + + +def test_list_pages_rest_flattened(): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = PagesClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.ListPagesResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.PagesGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.ListPagesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_pages(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/pages" + % client.transport._host, + args[1], + ) + + +def test_list_pages_rest_flattened_error(transport: str = "rest"): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.PagesGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_pages( + page.ListPagesRequest(), + parent="parent_value", + ) + + +def test_list_pages_rest_pager(transport: str = "rest"): + client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + page.ListPagesResponse( + pages=[ + page.Page(), + page.Page(), + page.Page(), + ], + next_page_token="abc", + ), + page.ListPagesResponse( + pages=[], + next_page_token="def", + ), + page.ListPagesResponse( + pages=[ + page.Page(), + ], + next_page_token="ghi", + ), + page.ListPagesResponse( + pages=[ + page.Page(), + page.Page(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(page.ListPagesResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.PagesGrpcTransport, - transports.PagesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_pages(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, page.Page) for i in results) + + pages = list(client.list_pages(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + page.GetPageRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = PagesClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_page_rest(request_type): client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.PagesGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) -def test_pages_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.PagesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_pages_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.pages.transports.PagesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.PagesTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_page(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_pages", - "get_page", - "create_page", - "update_page", - "delete_page", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + # Establish that the response is the type that we expect. + assert isinstance(response, page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_get_page_rest_required_fields(request_type=page.GetPageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_page._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_get_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_get_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = page.GetPageRequest.pb(page.GetPageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = page.Page.to_json(page.Page()) + + request = page.GetPageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = page.Page() + + client.get_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_page_rest_bad_request( + transport: str = "rest", request_type=page.GetPageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_page(request) + + +def test_get_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_get_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_page( + page.GetPageRequest(), + name="name_value", + ) + + +def test_get_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_page.CreatePageRequest, + dict, + ], +) +def test_create_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["page"] = { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_page(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_create_page_rest_required_fields(request_type=gcdc_page.CreatePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_page._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "page", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_create_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_create_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_page.CreatePageRequest.pb(gcdc_page.CreatePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_page.Page.to_json(gcdc_page.Page()) + + request = gcdc_page.CreatePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_page.Page() + + client.create_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_page_rest_bad_request( + transport: str = "rest", request_type=gcdc_page.CreatePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["page"] = { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_page(request) + + +def test_create_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + page=gcdc_page.Page(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/pages" + % client.transport._host, + args[1], + ) + + +def test_create_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_page( + gcdc_page.CreatePageRequest(), + parent="parent_value", + page=gcdc_page.Page(name="name_value"), + ) + + +def test_create_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_page.UpdatePageRequest, + dict, + ], +) +def test_update_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + request_init["page"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page( + name="name_value", + display_name="display_name_value", + transition_route_groups=["transition_route_groups_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_page(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_page.Page) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.transition_route_groups == ["transition_route_groups_value"] + + +def test_update_page_rest_required_fields(request_type=gcdc_page.UpdatePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_page._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("page",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "post_update_page" + ) as post, mock.patch.object( + transports.PagesRestInterceptor, "pre_update_page" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_page.UpdatePageRequest.pb(gcdc_page.UpdatePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_page.Page.to_json(gcdc_page.Page()) + + request = gcdc_page.UpdatePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_page.Page() + + client.update_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_page_rest_bad_request( + transport: str = "rest", request_type=gcdc_page.UpdatePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + request_init["page"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": {"phone_number": "phone_number_value"}, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [{"message": {}, "additional_cases": {}}], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_page(request) + + +def test_update_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_page.Page() + + # get arguments that satisfy an http rule for this method + sample_request = { + "page": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + page=gcdc_page.Page(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_page.Page.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{page.name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_update_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_page( + gcdc_page.UpdatePageRequest(), + page=gcdc_page.Page(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + page.DeletePageRequest, + dict, + ], +) +def test_delete_page_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_page(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_page_rest_required_fields(request_type=page.DeletePageRequest): + transport_class = transports.PagesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_page._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_page._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_page(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_page_rest_unset_required_fields(): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_page._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_page_rest_interceptors(null_interceptor): + transport = transports.PagesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PagesRestInterceptor(), + ) + client = PagesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.PagesRestInterceptor, "pre_delete_page" + ) as pre: + pre.assert_not_called() + pb_message = page.DeletePageRequest.pb(page.DeletePageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = page.DeletePageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_page( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_page_rest_bad_request( + transport: str = "rest", request_type=page.DeletePageRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_page(request) + + +def test_delete_page_rest_flattened(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/pages/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_page(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/pages/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_page_rest_flattened_error(transport: str = "rest"): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_page( + page.DeletePageRequest(), + name="name_value", + ) + + +def test_delete_page_rest_error(): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PagesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PagesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PagesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = PagesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.PagesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.PagesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.PagesGrpcTransport, + transports.PagesGrpcAsyncIOTransport, + transports.PagesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = PagesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.PagesGrpcTransport, + ) + + +def test_pages_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.PagesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_pages_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.pages.transports.PagesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.PagesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_pages", + "get_page", + "create_page", + "update_page", + "delete_page", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() def test_pages_base_transport_with_credentials_file(): @@ -2256,6 +4135,7 @@ def test_pages_transport_auth_adc(transport_class): [ transports.PagesGrpcTransport, transports.PagesGrpcAsyncIOTransport, + transports.PagesRestTransport, ], ) def test_pages_transport_auth_gdch_credentials(transport_class): @@ -2353,11 +4233,23 @@ def test_pages_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_pages_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.PagesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_pages_host_no_port(transport_name): @@ -2368,7 +4260,11 @@ def test_pages_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2376,6 +4272,7 @@ def test_pages_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_pages_host_with_port(transport_name): @@ -2386,7 +4283,45 @@ def test_pages_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_pages_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = PagesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = PagesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_pages._session + session2 = client2.transport.list_pages._session + assert session1 != session2 + session1 = client1.transport.get_page._session + session2 = client2.transport.get_page._session + assert session1 != session2 + session1 = client1.transport.create_page._session + session2 = client2.transport.create_page._session + assert session1 != session2 + session1 = client1.transport.update_page._session + session2 = client2.transport.update_page._session + assert session1 != session2 + session1 = client1.transport.delete_page._session + session2 = client2.transport.delete_page._session + assert session1 != session2 def test_pages_grpc_transport_channel(): @@ -2827,6 +4762,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = PagesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = PagesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3544,6 +5765,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3561,6 +5783,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_security_settings_service.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_security_settings_service.py index afa17471..bb266975 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_security_settings_service.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_security_settings_service.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -108,6 +115,7 @@ def test__get_default_mtls_endpoint(): [ (SecuritySettingsServiceClient, "grpc"), (SecuritySettingsServiceAsyncClient, "grpc_asyncio"), + (SecuritySettingsServiceClient, "rest"), ], ) def test_security_settings_service_client_from_service_account_info( @@ -123,7 +131,11 @@ def test_security_settings_service_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -131,6 +143,7 @@ def test_security_settings_service_client_from_service_account_info( [ (transports.SecuritySettingsServiceGrpcTransport, "grpc"), (transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SecuritySettingsServiceRestTransport, "rest"), ], ) def test_security_settings_service_client_service_account_always_use_jwt( @@ -156,6 +169,7 @@ def test_security_settings_service_client_service_account_always_use_jwt( [ (SecuritySettingsServiceClient, "grpc"), (SecuritySettingsServiceAsyncClient, "grpc_asyncio"), + (SecuritySettingsServiceClient, "rest"), ], ) def test_security_settings_service_client_from_service_account_file( @@ -178,13 +192,18 @@ def test_security_settings_service_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_security_settings_service_client_get_transport_class(): transport = SecuritySettingsServiceClient.get_transport_class() available_transports = [ transports.SecuritySettingsServiceGrpcTransport, + transports.SecuritySettingsServiceRestTransport, ] assert transport in available_transports @@ -205,6 +224,11 @@ def test_security_settings_service_client_get_transport_class(): transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + ), ], ) @mock.patch.object( @@ -360,6 +384,18 @@ def test_security_settings_service_client_client_options( "grpc_asyncio", "false", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + "true", + ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -565,6 +601,11 @@ def test_security_settings_service_client_get_mtls_endpoint_and_cert_source( transports.SecuritySettingsServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + ), ], ) def test_security_settings_service_client_client_options_scopes( @@ -605,6 +646,12 @@ def test_security_settings_service_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + SecuritySettingsServiceClient, + transports.SecuritySettingsServiceRestTransport, + "rest", + None, + ), ], ) def test_security_settings_service_client_client_options_credentials_file( @@ -2310,134 +2357,1737 @@ async def test_delete_security_settings_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + gcdc_security_settings.CreateSecuritySettingsRequest, + dict, + ], +) +def test_create_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["security_settings"] = { + "name": "name_value", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + assert ( + response.redaction_scope + == gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE + ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] + + +def test_create_security_settings_rest_required_fields( + request_type=gcdc_security_settings.CreateSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options=options, - transport=transport, - ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "securitySettings", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), ) - with pytest.raises(ValueError): - client = SecuritySettingsServiceClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "post_create_security_settings", + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_create_security_settings", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_security_settings.CreateSecuritySettingsRequest.pb( + gcdc_security_settings.CreateSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_security_settings.SecuritySettings.to_json( + gcdc_security_settings.SecuritySettings() ) + request = gcdc_security_settings.CreateSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_security_settings.SecuritySettings() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + client.create_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_security_settings_rest_bad_request( + transport: str = "rest", + request_type=gcdc_security_settings.CreateSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = SecuritySettingsServiceClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request_init["security_settings"] = { + "name": "name_value", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SecuritySettingsServiceGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_security_settings(request) + + +def test_create_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.SecuritySettingsServiceGrpcAsyncIOTransport( + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*}/securitySettings" + % client.transport._host, + args[1], + ) + + +def test_create_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_security_settings( + gcdc_security_settings.CreateSecuritySettingsRequest(), + parent="parent_value", + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.SecuritySettingsServiceGrpcTransport, - transports.SecuritySettingsServiceGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + +def test_create_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + security_settings.GetSecuritySettingsRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = SecuritySettingsServiceClient.get_transport_class(transport_name)( +def test_get_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = SecuritySettingsServiceClient( - credentials=ga_credentials.AnonymousCredentials(), + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE ) - assert isinstance( - client.transport, - transports.SecuritySettingsServiceGrpcTransport, + assert ( + response.redaction_scope + == security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] -def test_security_settings_service_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SecuritySettingsServiceTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +def test_get_security_settings_rest_required_fields( + request_type=security_settings.GetSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) + # verify fields with default values are dropped -def test_security_settings_service_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.security_settings_service.transports.SecuritySettingsServiceTransport.__init__" + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "post_get_security_settings" + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "pre_get_security_settings" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = security_settings.GetSecuritySettingsRequest.pb( + security_settings.GetSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = security_settings.SecuritySettings.to_json( + security_settings.SecuritySettings() + ) + + request = security_settings.GetSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = security_settings.SecuritySettings() + + client.get_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_security_settings_rest_bad_request( + transport: str = "rest", request_type=security_settings.GetSecuritySettingsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_security_settings(request) + + +def test_get_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_get_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_security_settings( + security_settings.GetSecuritySettingsRequest(), + name="name_value", + ) + + +def test_get_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_security_settings.UpdateSecuritySettingsRequest, + dict, + ], +) +def test_update_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + request_init["security_settings"] = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings( + name="name_value", + display_name="display_name_value", + redaction_strategy=gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE, + redaction_scope=gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE, + inspect_template="inspect_template_value", + deidentify_template="deidentify_template_value", + purge_data_types=[ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ], + retention_window_days=2271, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_security_settings.SecuritySettings) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert ( + response.redaction_strategy + == gcdc_security_settings.SecuritySettings.RedactionStrategy.REDACT_WITH_SERVICE + ) + assert ( + response.redaction_scope + == gcdc_security_settings.SecuritySettings.RedactionScope.REDACT_DISK_STORAGE + ) + assert response.inspect_template == "inspect_template_value" + assert response.deidentify_template == "deidentify_template_value" + assert response.purge_data_types == [ + gcdc_security_settings.SecuritySettings.PurgeDataType.DIALOGFLOW_HISTORY + ] + + +def test_update_security_settings_rest_required_fields( + request_type=gcdc_security_settings.UpdateSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_security_settings._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "securitySettings", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "post_update_security_settings", + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_update_security_settings", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_security_settings.UpdateSecuritySettingsRequest.pb( + gcdc_security_settings.UpdateSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_security_settings.SecuritySettings.to_json( + gcdc_security_settings.SecuritySettings() + ) + + request = gcdc_security_settings.UpdateSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_security_settings.SecuritySettings() + + client.update_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_security_settings_rest_bad_request( + transport: str = "rest", + request_type=gcdc_security_settings.UpdateSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + request_init["security_settings"] = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3", + "display_name": "display_name_value", + "redaction_strategy": 1, + "redaction_scope": 2, + "inspect_template": "inspect_template_value", + "deidentify_template": "deidentify_template_value", + "retention_window_days": 2271, + "purge_data_types": [1], + "audio_export_settings": { + "gcs_bucket": "gcs_bucket_value", + "audio_export_pattern": "audio_export_pattern_value", + "enable_audio_redaction": True, + "audio_format": 1, + }, + "insights_export_settings": {"enable_insights_export": True}, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_security_settings(request) + + +def test_update_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_security_settings.SecuritySettings() + + # get arguments that satisfy an http rule for this method + sample_request = { + "security_settings": { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + } + + # get truthy value for each flattened field + mock_args = dict( + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_security_settings.SecuritySettings.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{security_settings.name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_update_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_security_settings( + gcdc_security_settings.UpdateSecuritySettingsRequest(), + security_settings=gcdc_security_settings.SecuritySettings( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + security_settings.ListSecuritySettingsRequest, + dict, + ], +) +def test_list_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_security_settings(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSecuritySettingsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_security_settings_rest_required_fields( + request_type=security_settings.ListSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_security_settings._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "post_list_security_settings" + ) as post, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, "pre_list_security_settings" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = security_settings.ListSecuritySettingsRequest.pb( + security_settings.ListSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + security_settings.ListSecuritySettingsResponse.to_json( + security_settings.ListSecuritySettingsResponse() + ) + ) + + request = security_settings.ListSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = security_settings.ListSecuritySettingsResponse() + + client.list_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_security_settings_rest_bad_request( + transport: str = "rest", request_type=security_settings.ListSecuritySettingsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_security_settings(request) + + +def test_list_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = security_settings.ListSecuritySettingsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = security_settings.ListSecuritySettingsResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*}/securitySettings" + % client.transport._host, + args[1], + ) + + +def test_list_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_security_settings( + security_settings.ListSecuritySettingsRequest(), + parent="parent_value", + ) + + +def test_list_security_settings_rest_pager(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + ], + next_page_token="abc", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[], + next_page_token="def", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + ], + next_page_token="ghi", + ), + security_settings.ListSecuritySettingsResponse( + security_settings=[ + security_settings.SecuritySettings(), + security_settings.SecuritySettings(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + security_settings.ListSecuritySettingsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "projects/sample1/locations/sample2"} + + pager = client.list_security_settings(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, security_settings.SecuritySettings) for i in results) + + pages = list(client.list_security_settings(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + security_settings.DeleteSecuritySettingsRequest, + dict, + ], +) +def test_delete_security_settings_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_security_settings(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_security_settings_rest_required_fields( + request_type=security_settings.DeleteSecuritySettingsRequest, +): + transport_class = transports.SecuritySettingsServiceRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_security_settings._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_security_settings(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_security_settings_rest_unset_required_fields(): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_security_settings._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_security_settings_rest_interceptors(null_interceptor): + transport = transports.SecuritySettingsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SecuritySettingsServiceRestInterceptor(), + ) + client = SecuritySettingsServiceClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SecuritySettingsServiceRestInterceptor, + "pre_delete_security_settings", + ) as pre: + pre.assert_not_called() + pb_message = security_settings.DeleteSecuritySettingsRequest.pb( + security_settings.DeleteSecuritySettingsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = security_settings.DeleteSecuritySettingsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_security_settings( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_security_settings_rest_bad_request( + transport: str = "rest", + request_type=security_settings.DeleteSecuritySettingsRequest, +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_security_settings(request) + + +def test_delete_security_settings_rest_flattened(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/securitySettings/sample3" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_security_settings(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/securitySettings/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_security_settings_rest_flattened_error(transport: str = "rest"): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_security_settings( + security_settings.DeleteSecuritySettingsRequest(), + name="name_value", + ) + + +def test_delete_security_settings_rest_error(): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SecuritySettingsServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SecuritySettingsServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SecuritySettingsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SecuritySettingsServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SecuritySettingsServiceGrpcTransport, + transports.SecuritySettingsServiceGrpcAsyncIOTransport, + transports.SecuritySettingsServiceRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SecuritySettingsServiceClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SecuritySettingsServiceGrpcTransport, + ) + + +def test_security_settings_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SecuritySettingsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_security_settings_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.security_settings_service.transports.SecuritySettingsServiceTransport.__init__" ) as Transport: Transport.return_value = None transport = transports.SecuritySettingsServiceTransport( @@ -2552,6 +4202,7 @@ def test_security_settings_service_transport_auth_adc(transport_class): [ transports.SecuritySettingsServiceGrpcTransport, transports.SecuritySettingsServiceGrpcAsyncIOTransport, + transports.SecuritySettingsServiceRestTransport, ], ) def test_security_settings_service_transport_auth_gdch_credentials(transport_class): @@ -2656,11 +4307,23 @@ def test_security_settings_service_grpc_transport_client_cert_source_for_mtls( ) +def test_security_settings_service_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SecuritySettingsServiceRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_security_settings_service_host_no_port(transport_name): @@ -2671,7 +4334,11 @@ def test_security_settings_service_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2679,6 +4346,7 @@ def test_security_settings_service_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_security_settings_service_host_with_port(transport_name): @@ -2689,7 +4357,45 @@ def test_security_settings_service_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_security_settings_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SecuritySettingsServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SecuritySettingsServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_security_settings._session + session2 = client2.transport.create_security_settings._session + assert session1 != session2 + session1 = client1.transport.get_security_settings._session + session2 = client2.transport.get_security_settings._session + assert session1 != session2 + session1 = client1.transport.update_security_settings._session + session2 = client2.transport.update_security_settings._session + assert session1 != session2 + session1 = client1.transport.list_security_settings._session + session2 = client2.transport.list_security_settings._session + assert session1 != session2 + session1 = client1.transport.delete_security_settings._session + session2 = client2.transport.delete_security_settings._session + assert session1 != session2 def test_security_settings_service_grpc_transport_channel(): @@ -3042,6 +4748,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SecuritySettingsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SecuritySettingsServiceClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3763,6 +5755,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3780,6 +5773,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_session_entity_types.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_session_entity_types.py index a8cc1b7d..d491ee03 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_session_entity_types.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_session_entity_types.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -107,6 +114,7 @@ def test__get_default_mtls_endpoint(): [ (SessionEntityTypesClient, "grpc"), (SessionEntityTypesAsyncClient, "grpc_asyncio"), + (SessionEntityTypesClient, "rest"), ], ) def test_session_entity_types_client_from_service_account_info( @@ -122,7 +130,11 @@ def test_session_entity_types_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -130,6 +142,7 @@ def test_session_entity_types_client_from_service_account_info( [ (transports.SessionEntityTypesGrpcTransport, "grpc"), (transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SessionEntityTypesRestTransport, "rest"), ], ) def test_session_entity_types_client_service_account_always_use_jwt( @@ -155,6 +168,7 @@ def test_session_entity_types_client_service_account_always_use_jwt( [ (SessionEntityTypesClient, "grpc"), (SessionEntityTypesAsyncClient, "grpc_asyncio"), + (SessionEntityTypesClient, "rest"), ], ) def test_session_entity_types_client_from_service_account_file( @@ -177,13 +191,18 @@ def test_session_entity_types_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_session_entity_types_client_get_transport_class(): transport = SessionEntityTypesClient.get_transport_class() available_transports = [ transports.SessionEntityTypesGrpcTransport, + transports.SessionEntityTypesRestTransport, ] assert transport in available_transports @@ -200,6 +219,7 @@ def test_session_entity_types_client_get_transport_class(): transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (SessionEntityTypesClient, transports.SessionEntityTypesRestTransport, "rest"), ], ) @mock.patch.object( @@ -355,6 +375,18 @@ def test_session_entity_types_client_client_options( "grpc_asyncio", "false", ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + "true", + ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -554,6 +586,7 @@ def test_session_entity_types_client_get_mtls_endpoint_and_cert_source(client_cl transports.SessionEntityTypesGrpcAsyncIOTransport, "grpc_asyncio", ), + (SessionEntityTypesClient, transports.SessionEntityTypesRestTransport, "rest"), ], ) def test_session_entity_types_client_client_options_scopes( @@ -594,6 +627,12 @@ def test_session_entity_types_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + SessionEntityTypesClient, + transports.SessionEntityTypesRestTransport, + "rest", + None, + ), ], ) def test_session_entity_types_client_client_options_credentials_file( @@ -2198,222 +2237,1741 @@ async def test_delete_session_entity_type_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.ListSessionEntityTypesRequest, + dict, + ], +) +def test_list_session_entity_types_rest(request_type): + client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SessionEntityTypesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_session_entity_types(request) + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSessionEntityTypesPager) + assert response.next_page_token == "next_page_token_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + +def test_list_session_entity_types_rest_required_fields( + request_type=session_entity_type.ListSessionEntityTypesRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - client = SessionEntityTypesClient(transport=transport) - assert client.transport is transport + # verify fields with default values are dropped -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SessionEntityTypesGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_session_entity_types._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_session_entity_types._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) ) - channel = transport.grpc_channel - assert channel + jsonified_request.update(unset_fields) - transport = transports.SessionEntityTypesGrpcAsyncIOTransport( + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -@pytest.mark.parametrize( - "transport_class", - [ - transports.SessionEntityTypesGrpcTransport, - transports.SessionEntityTypesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = SessionEntityTypesClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name + response = client.list_session_entity_types(request) + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = SessionEntityTypesClient( - credentials=ga_credentials.AnonymousCredentials(), + +def test_list_session_entity_types_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.SessionEntityTypesGrpcTransport, + + unset_fields = transport.list_session_entity_types._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) ) -def test_session_entity_types_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SessionEntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_session_entity_types_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_list_session_entity_types" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_list_session_entity_types" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session_entity_type.ListSessionEntityTypesRequest.pb( + session_entity_type.ListSessionEntityTypesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + session_entity_type.ListSessionEntityTypesResponse.to_json( + session_entity_type.ListSessionEntityTypesResponse() + ) ) + request = session_entity_type.ListSessionEntityTypesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session_entity_type.ListSessionEntityTypesResponse() -def test_session_entity_types_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.SessionEntityTypesTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.list_session_entity_types( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_session_entity_types", - "get_session_entity_type", - "create_session_entity_type", - "update_session_entity_type", - "delete_session_entity_type", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + pre.assert_called_once() + post.assert_called_once() + + +def test_list_session_entity_types_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.ListSessionEntityTypesRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_session_entity_types(request) -def test_session_entity_types_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.SessionEntityTypesTransport( - credentials_file="credentials.json", - quota_project_id="octopus", +def test_list_session_entity_types_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.ListSessionEntityTypesResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.ListSessionEntityTypesResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + client.list_session_entity_types(**mock_args) -def test_session_entity_types_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.SessionEntityTypesTransport() - adc.assert_called_once() + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes" + % client.transport._host, + args[1], + ) -def test_session_entity_types_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - SessionEntityTypesClient() - adc.assert_called_once_with( - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id=None, +def test_list_session_entity_types_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_session_entity_types( + session_entity_type.ListSessionEntityTypesRequest(), + parent="parent_value", ) -@pytest.mark.parametrize( - "transport_class", +def test_list_session_entity_types_rest_pager(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + ], + next_page_token="abc", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[], + next_page_token="def", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + ], + next_page_token="ghi", + ), + session_entity_type.ListSessionEntityTypesResponse( + session_entity_types=[ + session_entity_type.SessionEntityType(), + session_entity_type.SessionEntityType(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + session_entity_type.ListSessionEntityTypesResponse.to_json(x) + for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + pager = client.list_session_entity_types(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, session_entity_type.SessionEntityType) for i in results + ) + + pages = list(client.list_session_entity_types(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.GetSessionEntityTypeRequest, + dict, + ], +) +def test_get_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_get_session_entity_type_rest_required_fields( + request_type=session_entity_type.GetSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_get_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_get_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session_entity_type.GetSessionEntityTypeRequest.pb( + session_entity_type.GetSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session_entity_type.SessionEntityType.to_json( + session_entity_type.SessionEntityType() + ) + + request = session_entity_type.GetSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session_entity_type.SessionEntityType() + + client.get_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.GetSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_session_entity_type(request) + + +def test_get_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_get_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_session_entity_type( + session_entity_type.GetSessionEntityTypeRequest(), + name="name_value", + ) + + +def test_get_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_session_entity_type.CreateSessionEntityTypeRequest, + dict, + ], +) +def test_create_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request_init["session_entity_type"] = { + "name": "name_value", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_create_session_entity_type_rest_required_fields( + request_type=gcdc_session_entity_type.CreateSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "sessionEntityType", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_create_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_create_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_session_entity_type.CreateSessionEntityTypeRequest.pb( + gcdc_session_entity_type.CreateSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_session_entity_type.SessionEntityType.to_json( + gcdc_session_entity_type.SessionEntityType() + ) + + request = gcdc_session_entity_type.CreateSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_session_entity_type.SessionEntityType() + + client.create_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=gcdc_session_entity_type.CreateSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request_init["session_entity_type"] = { + "name": "name_value", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_session_entity_type(request) + + +def test_create_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/sessions/*}/entityTypes" + % client.transport._host, + args[1], + ) + + +def test_create_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_session_entity_type( + gcdc_session_entity_type.CreateSessionEntityTypeRequest(), + parent="parent_value", + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + ) + + +def test_create_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_session_entity_type.UpdateSessionEntityTypeRequest, + dict, + ], +) +def test_update_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + request_init["session_entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType( + name="name_value", + entity_override_mode=gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_session_entity_type.SessionEntityType) + assert response.name == "name_value" + assert ( + response.entity_override_mode + == gcdc_session_entity_type.SessionEntityType.EntityOverrideMode.ENTITY_OVERRIDE_MODE_OVERRIDE + ) + + +def test_update_session_entity_type_rest_required_fields( + request_type=gcdc_session_entity_type.UpdateSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_session_entity_type._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("sessionEntityType",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "post_update_session_entity_type" + ) as post, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_update_session_entity_type" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_session_entity_type.UpdateSessionEntityTypeRequest.pb( + gcdc_session_entity_type.UpdateSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_session_entity_type.SessionEntityType.to_json( + gcdc_session_entity_type.SessionEntityType() + ) + + request = gcdc_session_entity_type.UpdateSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_session_entity_type.SessionEntityType() + + client.update_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=gcdc_session_entity_type.UpdateSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + request_init["session_entity_type"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5", + "entity_override_mode": 1, + "entities": [ + {"value": "value_value", "synonyms": ["synonyms_value1", "synonyms_value2"]} + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_session_entity_type(request) + + +def test_update_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_session_entity_type.SessionEntityType() + + # get arguments that satisfy an http rule for this method + sample_request = { + "session_entity_type": { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_session_entity_type.SessionEntityType.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{session_entity_type.name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_session_entity_type( + gcdc_session_entity_type.UpdateSessionEntityTypeRequest(), + session_entity_type=gcdc_session_entity_type.SessionEntityType( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + session_entity_type.DeleteSessionEntityTypeRequest, + dict, + ], +) +def test_delete_session_entity_type_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_session_entity_type(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_session_entity_type_rest_required_fields( + request_type=session_entity_type.DeleteSessionEntityTypeRequest, +): + transport_class = transports.SessionEntityTypesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_session_entity_type._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_session_entity_type(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_session_entity_type_rest_unset_required_fields(): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_session_entity_type._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_session_entity_type_rest_interceptors(null_interceptor): + transport = transports.SessionEntityTypesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SessionEntityTypesRestInterceptor(), + ) + client = SessionEntityTypesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionEntityTypesRestInterceptor, "pre_delete_session_entity_type" + ) as pre: + pre.assert_not_called() + pb_message = session_entity_type.DeleteSessionEntityTypeRequest.pb( + session_entity_type.DeleteSessionEntityTypeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = session_entity_type.DeleteSessionEntityTypeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_session_entity_type( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_session_entity_type_rest_bad_request( + transport: str = "rest", + request_type=session_entity_type.DeleteSessionEntityTypeRequest, +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_session_entity_type(request) + + +def test_delete_session_entity_type_rest_flattened(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4/entityTypes/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_session_entity_type(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/sessions/*/entityTypes/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_session_entity_type_rest_flattened_error(transport: str = "rest"): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_session_entity_type( + session_entity_type.DeleteSessionEntityTypeRequest(), + name="name_value", + ) + + +def test_delete_session_entity_type_rest_error(): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SessionEntityTypesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SessionEntityTypesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SessionEntityTypesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SessionEntityTypesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SessionEntityTypesGrpcTransport, + transports.SessionEntityTypesGrpcAsyncIOTransport, + transports.SessionEntityTypesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SessionEntityTypesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SessionEntityTypesGrpcTransport, + ) + + +def test_session_entity_types_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SessionEntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_session_entity_types_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.SessionEntityTypesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_session_entity_types", + "get_session_entity_type", + "create_session_entity_type", + "update_session_entity_type", + "delete_session_entity_type", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_session_entity_types_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.SessionEntityTypesTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_session_entity_types_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.session_entity_types.transports.SessionEntityTypesTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.SessionEntityTypesTransport() + adc.assert_called_once() + + +def test_session_entity_types_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + SessionEntityTypesClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", [ transports.SessionEntityTypesGrpcTransport, transports.SessionEntityTypesGrpcAsyncIOTransport, @@ -2440,6 +3998,7 @@ def test_session_entity_types_transport_auth_adc(transport_class): [ transports.SessionEntityTypesGrpcTransport, transports.SessionEntityTypesGrpcAsyncIOTransport, + transports.SessionEntityTypesRestTransport, ], ) def test_session_entity_types_transport_auth_gdch_credentials(transport_class): @@ -2542,11 +4101,23 @@ def test_session_entity_types_grpc_transport_client_cert_source_for_mtls( ) +def test_session_entity_types_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SessionEntityTypesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_session_entity_types_host_no_port(transport_name): @@ -2557,7 +4128,11 @@ def test_session_entity_types_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2565,6 +4140,7 @@ def test_session_entity_types_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_session_entity_types_host_with_port(transport_name): @@ -2575,7 +4151,45 @@ def test_session_entity_types_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_session_entity_types_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SessionEntityTypesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SessionEntityTypesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_session_entity_types._session + session2 = client2.transport.list_session_entity_types._session + assert session1 != session2 + session1 = client1.transport.get_session_entity_type._session + session2 = client2.transport.get_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.create_session_entity_type._session + session2 = client2.transport.create_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.update_session_entity_type._session + session2 = client2.transport.update_session_entity_type._session + assert session1 != session2 + session1 = client1.transport.delete_session_entity_type._session + session2 = client2.transport.delete_session_entity_type._session + assert session1 != session2 def test_session_entity_types_grpc_transport_channel(): @@ -2878,6 +4492,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SessionEntityTypesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SessionEntityTypesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3597,6 +5497,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3614,6 +5515,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_sessions.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_sessions.py index a3a392aa..bf2f8855 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_sessions.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_sessions.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -98,6 +105,7 @@ def test__get_default_mtls_endpoint(): [ (SessionsClient, "grpc"), (SessionsAsyncClient, "grpc_asyncio"), + (SessionsClient, "rest"), ], ) def test_sessions_client_from_service_account_info(client_class, transport_name): @@ -111,7 +119,11 @@ def test_sessions_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -119,6 +131,7 @@ def test_sessions_client_from_service_account_info(client_class, transport_name) [ (transports.SessionsGrpcTransport, "grpc"), (transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SessionsRestTransport, "rest"), ], ) def test_sessions_client_service_account_always_use_jwt( @@ -144,6 +157,7 @@ def test_sessions_client_service_account_always_use_jwt( [ (SessionsClient, "grpc"), (SessionsAsyncClient, "grpc_asyncio"), + (SessionsClient, "rest"), ], ) def test_sessions_client_from_service_account_file(client_class, transport_name): @@ -164,13 +178,18 @@ def test_sessions_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_sessions_client_get_transport_class(): transport = SessionsClient.get_transport_class() available_transports = [ transports.SessionsGrpcTransport, + transports.SessionsRestTransport, ] assert transport in available_transports @@ -183,6 +202,7 @@ def test_sessions_client_get_transport_class(): [ (SessionsClient, transports.SessionsGrpcTransport, "grpc"), (SessionsAsyncClient, transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (SessionsClient, transports.SessionsRestTransport, "rest"), ], ) @mock.patch.object( @@ -324,6 +344,8 @@ def test_sessions_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (SessionsClient, transports.SessionsRestTransport, "rest", "true"), + (SessionsClient, transports.SessionsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -513,6 +535,7 @@ def test_sessions_client_get_mtls_endpoint_and_cert_source(client_class): [ (SessionsClient, transports.SessionsGrpcTransport, "grpc"), (SessionsAsyncClient, transports.SessionsGrpcAsyncIOTransport, "grpc_asyncio"), + (SessionsClient, transports.SessionsRestTransport, "rest"), ], ) def test_sessions_client_client_options_scopes( @@ -548,6 +571,7 @@ def test_sessions_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (SessionsClient, transports.SessionsRestTransport, "rest", None), ], ) def test_sessions_client_client_options_credentials_file( @@ -1194,6 +1218,605 @@ async def test_fulfill_intent_field_headers_async(): ) in kw["metadata"] +@pytest.mark.parametrize( + "request_type", + [ + session.DetectIntentRequest, + dict, + ], +) +def test_detect_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.DetectIntentResponse( + response_id="response_id_value", + output_audio=b"output_audio_blob", + response_type=session.DetectIntentResponse.ResponseType.PARTIAL, + allow_cancellation=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.DetectIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.detect_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.DetectIntentResponse) + assert response.response_id == "response_id_value" + assert response.output_audio == b"output_audio_blob" + assert response.response_type == session.DetectIntentResponse.ResponseType.PARTIAL + assert response.allow_cancellation is True + + +def test_detect_intent_rest_required_fields(request_type=session.DetectIntentRequest): + transport_class = transports.SessionsRestTransport + + request_init = {} + request_init["session"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detect_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["session"] = "session_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detect_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "session" in jsonified_request + assert jsonified_request["session"] == "session_value" + + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session.DetectIntentResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session.DetectIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.detect_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_detect_intent_rest_unset_required_fields(): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.detect_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "session", + "queryInput", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_detect_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_detect_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_detect_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.DetectIntentRequest.pb(session.DetectIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.DetectIntentResponse.to_json( + session.DetectIntentResponse() + ) + + request = session.DetectIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.DetectIntentResponse() + + client.detect_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_detect_intent_rest_bad_request( + transport: str = "rest", request_type=session.DetectIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.detect_intent(request) + + +def test_detect_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_detect_intent_rest_no_http_options(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = session.StreamingDetectIntentRequest() + requests = [request] + with pytest.raises(RuntimeError): + client.streaming_detect_intent(requests) + + +@pytest.mark.parametrize( + "request_type", + [ + session.MatchIntentRequest, + dict, + ], +) +def test_match_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.MatchIntentResponse( + text="text_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.MatchIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.match_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.MatchIntentResponse) + + +def test_match_intent_rest_required_fields(request_type=session.MatchIntentRequest): + transport_class = transports.SessionsRestTransport + + request_init = {} + request_init["session"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).match_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["session"] = "session_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).match_intent._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "session" in jsonified_request + assert jsonified_request["session"] == "session_value" + + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = session.MatchIntentResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = session.MatchIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.match_intent(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_match_intent_rest_unset_required_fields(): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.match_intent._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "session", + "queryInput", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_match_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_match_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_match_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.MatchIntentRequest.pb(session.MatchIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.MatchIntentResponse.to_json( + session.MatchIntentResponse() + ) + + request = session.MatchIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.MatchIntentResponse() + + client.match_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_match_intent_rest_bad_request( + transport: str = "rest", request_type=session.MatchIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.match_intent(request) + + +def test_match_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + session.FulfillIntentRequest, + dict, + ], +) +def test_fulfill_intent_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "match_intent_request": { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = session.FulfillIntentResponse( + response_id="response_id_value", + output_audio=b"output_audio_blob", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = session.FulfillIntentResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.fulfill_intent(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, session.FulfillIntentResponse) + assert response.response_id == "response_id_value" + assert response.output_audio == b"output_audio_blob" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_fulfill_intent_rest_interceptors(null_interceptor): + transport = transports.SessionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.SessionsRestInterceptor(), + ) + client = SessionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.SessionsRestInterceptor, "post_fulfill_intent" + ) as post, mock.patch.object( + transports.SessionsRestInterceptor, "pre_fulfill_intent" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = session.FulfillIntentRequest.pb(session.FulfillIntentRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = session.FulfillIntentResponse.to_json( + session.FulfillIntentResponse() + ) + + request = session.FulfillIntentRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = session.FulfillIntentResponse() + + client.fulfill_intent( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_fulfill_intent_rest_bad_request( + transport: str = "rest", request_type=session.FulfillIntentRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "match_intent_request": { + "session": "projects/sample1/locations/sample2/agents/sample3/sessions/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.fulfill_intent(request) + + +def test_fulfill_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_detect_intent_rest_error(): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # Since a `google.api.http` annotation is required for using a rest transport + # method, this should error. + with pytest.raises(NotImplementedError) as not_implemented_error: + client.streaming_detect_intent({}) + assert "Method StreamingDetectIntent is not available over REST transport" in str( + not_implemented_error.value + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.SessionsGrpcTransport( @@ -1275,6 +1898,7 @@ def test_transport_get_channel(): [ transports.SessionsGrpcTransport, transports.SessionsGrpcAsyncIOTransport, + transports.SessionsRestTransport, ], ) def test_transport_adc(transport_class): @@ -1289,6 +1913,7 @@ def test_transport_adc(transport_class): "transport_name", [ "grpc", + "rest", ], ) def test_transport_kind(transport_name): @@ -1435,6 +2060,7 @@ def test_sessions_transport_auth_adc(transport_class): [ transports.SessionsGrpcTransport, transports.SessionsGrpcAsyncIOTransport, + transports.SessionsRestTransport, ], ) def test_sessions_transport_auth_gdch_credentials(transport_class): @@ -1532,11 +2158,23 @@ def test_sessions_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_sessions_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.SessionsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_sessions_host_no_port(transport_name): @@ -1547,7 +2185,11 @@ def test_sessions_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -1555,6 +2197,7 @@ def test_sessions_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_sessions_host_with_port(transport_name): @@ -1565,7 +2208,42 @@ def test_sessions_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_sessions_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SessionsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SessionsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.detect_intent._session + session2 = client2.transport.detect_intent._session + assert session1 != session2 + session1 = client1.transport.streaming_detect_intent._session + session2 = client2.transport.streaming_detect_intent._session + assert session1 != session2 + session1 = client1.transport.match_intent._session + session2 = client2.transport.match_intent._session + assert session1 != session2 + session1 = client1.transport.fulfill_intent._session + session2 = client2.transport.fulfill_intent._session + assert session1 != session2 def test_sessions_grpc_transport_channel(): @@ -2105,6 +2783,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = SessionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = SessionsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -2822,6 +3786,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -2839,6 +3804,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_test_cases.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_test_cases.py index 7c198b6a..016088fe 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_test_cases.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_test_cases.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -108,6 +115,7 @@ def test__get_default_mtls_endpoint(): [ (TestCasesClient, "grpc"), (TestCasesAsyncClient, "grpc_asyncio"), + (TestCasesClient, "rest"), ], ) def test_test_cases_client_from_service_account_info(client_class, transport_name): @@ -121,7 +129,11 @@ def test_test_cases_client_from_service_account_info(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -129,6 +141,7 @@ def test_test_cases_client_from_service_account_info(client_class, transport_nam [ (transports.TestCasesGrpcTransport, "grpc"), (transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.TestCasesRestTransport, "rest"), ], ) def test_test_cases_client_service_account_always_use_jwt( @@ -154,6 +167,7 @@ def test_test_cases_client_service_account_always_use_jwt( [ (TestCasesClient, "grpc"), (TestCasesAsyncClient, "grpc_asyncio"), + (TestCasesClient, "rest"), ], ) def test_test_cases_client_from_service_account_file(client_class, transport_name): @@ -174,13 +188,18 @@ def test_test_cases_client_from_service_account_file(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_test_cases_client_get_transport_class(): transport = TestCasesClient.get_transport_class() available_transports = [ transports.TestCasesGrpcTransport, + transports.TestCasesRestTransport, ] assert transport in available_transports @@ -197,6 +216,7 @@ def test_test_cases_client_get_transport_class(): transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest"), ], ) @mock.patch.object( @@ -340,6 +360,8 @@ def test_test_cases_client_client_options( "grpc_asyncio", "false", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest", "true"), + (TestCasesClient, transports.TestCasesRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -533,6 +555,7 @@ def test_test_cases_client_get_mtls_endpoint_and_cert_source(client_class): transports.TestCasesGrpcAsyncIOTransport, "grpc_asyncio", ), + (TestCasesClient, transports.TestCasesRestTransport, "rest"), ], ) def test_test_cases_client_client_options_scopes( @@ -568,6 +591,7 @@ def test_test_cases_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (TestCasesClient, transports.TestCasesRestTransport, "rest", None), ], ) def test_test_cases_client_client_options_credentials_file( @@ -3543,149 +3567,4113 @@ async def test_get_test_case_result_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TestCasesGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + test_case.ListTestCasesRequest, + dict, + ], +) +def test_list_test_cases_rest(request_type): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = TestCasesClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.TestCasesGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_test_cases(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTestCasesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_test_cases_rest_required_fields( + request_type=test_case.ListTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_cases._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + "view", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - with pytest.raises(ValueError): - client = TestCasesClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + + unset_fields = transport.list_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + "view", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.TestCasesGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TestCasesClient( - client_options=options, - transport=transport, + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_list_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_list_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ListTestCasesRequest.pb(test_case.ListTestCasesRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.ListTestCasesResponse.to_json( + test_case.ListTestCasesResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TestCasesClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = test_case.ListTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.ListTestCasesResponse() + + client.list_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.TestCasesGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ListTestCasesRequest +): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = TestCasesClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TestCasesGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_test_cases(request) + + +def test_list_test_cases_rest_flattened(): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = TestCasesClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCasesResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.TestCasesGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCasesResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_test_cases(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases" + % client.transport._host, + args[1], + ) + + +def test_list_test_cases_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.TestCasesGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_test_cases( + test_case.ListTestCasesRequest(), + parent="parent_value", + ) + + +def test_list_test_cases_rest_pager(transport: str = "rest"): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + test_case.TestCase(), + test_case.TestCase(), + ], + next_page_token="abc", + ), + test_case.ListTestCasesResponse( + test_cases=[], + next_page_token="def", + ), + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + ], + next_page_token="ghi", + ), + test_case.ListTestCasesResponse( + test_cases=[ + test_case.TestCase(), + test_case.TestCase(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(test_case.ListTestCasesResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.TestCasesGrpcTransport, - transports.TestCasesGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_test_cases(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, test_case.TestCase) for i in results) + + pages = list(client.list_test_cases(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + test_case.BatchDeleteTestCasesRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = TestCasesClient.get_transport_class(transport_name)( +def test_batch_delete_test_cases_rest(request_type): + client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.batch_delete_test_cases(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_batch_delete_test_cases_rest_required_fields( + request_type=test_case.BatchDeleteTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request_init["names"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_delete_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + jsonified_request["names"] = "names_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_delete_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + assert "names" in jsonified_request + assert jsonified_request["names"] == "names_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.batch_delete_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_batch_delete_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.TestCasesGrpcTransport, + + unset_fields = transport.batch_delete_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "names", + ) + ) ) -def test_test_cases_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.TestCasesTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_batch_delete_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_batch_delete_test_cases" + ) as pre: + pre.assert_not_called() + pb_message = test_case.BatchDeleteTestCasesRequest.pb( + test_case.BatchDeleteTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = test_case.BatchDeleteTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.batch_delete_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() -def test_test_cases_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.test_cases.transports.TestCasesTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.TestCasesTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_test_cases", - "batch_delete_test_cases", - "get_test_case", - "create_test_case", - "update_test_case", - "run_test_case", +def test_batch_delete_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.BatchDeleteTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.batch_delete_test_cases(request) + + +def test_batch_delete_test_cases_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.batch_delete_test_cases(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases:batchDelete" + % client.transport._host, + args[1], + ) + + +def test_batch_delete_test_cases_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.batch_delete_test_cases( + test_case.BatchDeleteTestCasesRequest(), + parent="parent_value", + ) + + +def test_batch_delete_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.GetTestCaseRequest, + dict, + ], +) +def test_get_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_get_test_case_rest_required_fields(request_type=test_case.GetTestCaseRequest): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_test_case._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_get_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_get_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.GetTestCaseRequest.pb(test_case.GetTestCaseRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.TestCase.to_json(test_case.TestCase()) + + request = test_case.GetTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.TestCase() + + client.get_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_test_case_rest_bad_request( + transport: str = "rest", request_type=test_case.GetTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_test_case(request) + + +def test_get_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/testCases/*}" + % client.transport._host, + args[1], + ) + + +def test_get_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_test_case( + test_case.GetTestCaseRequest(), + name="name_value", + ) + + +def test_get_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_test_case.CreateTestCaseRequest, + dict, + ], +) +def test_create_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["test_case"] = { + "name": "name_value", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_create_test_case_rest_required_fields( + request_type=gcdc_test_case.CreateTestCaseRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_test_case._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "testCase", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_create_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_create_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_test_case.CreateTestCaseRequest.pb( + gcdc_test_case.CreateTestCaseRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_test_case.TestCase.to_json( + gcdc_test_case.TestCase() + ) + + request = gcdc_test_case.CreateTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_test_case.TestCase() + + client.create_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_test_case_rest_bad_request( + transport: str = "rest", request_type=gcdc_test_case.CreateTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["test_case"] = { + "name": "name_value", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_test_case(request) + + +def test_create_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + test_case=gcdc_test_case.TestCase(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/testCases" + % client.transport._host, + args[1], + ) + + +def test_create_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_test_case( + gcdc_test_case.CreateTestCaseRequest(), + parent="parent_value", + test_case=gcdc_test_case.TestCase(name="name_value"), + ) + + +def test_create_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_test_case.UpdateTestCaseRequest, + dict, + ], +) +def test_update_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + request_init["test_case"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase( + name="name_value", + tags=["tags_value"], + display_name="display_name_value", + notes="notes_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_test_case(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_test_case.TestCase) + assert response.name == "name_value" + assert response.tags == ["tags_value"] + assert response.display_name == "display_name_value" + assert response.notes == "notes_value" + + +def test_update_test_case_rest_required_fields( + request_type=gcdc_test_case.UpdateTestCaseRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_test_case._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_test_case._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "testCase", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_update_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_update_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_test_case.UpdateTestCaseRequest.pb( + gcdc_test_case.UpdateTestCaseRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_test_case.TestCase.to_json( + gcdc_test_case.TestCase() + ) + + request = gcdc_test_case.UpdateTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_test_case.TestCase() + + client.update_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_test_case_rest_bad_request( + transport: str = "rest", request_type=gcdc_test_case.UpdateTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + request_init["test_case"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4", + "tags": ["tags_value1", "tags_value2"], + "display_name": "display_name_value", + "notes": "notes_value", + "test_config": { + "tracking_parameters": [ + "tracking_parameters_value1", + "tracking_parameters_value2", + ], + "flow": "flow_value", + "page": "page_value", + }, + "test_case_conversation_turns": [ + { + "user_input": { + "input": { + "text": {"text": "text_value"}, + "intent": {"intent": "intent_value"}, + "audio": { + "config": { + "audio_encoding": 1, + "sample_rate_hertz": 1817, + "enable_word_info": True, + "phrase_hints": [ + "phrase_hints_value1", + "phrase_hints_value2", + ], + "model": "model_value", + "model_variant": 1, + "single_utterance": True, + }, + "audio": b"audio_blob", + }, + "event": {"event": "event_value"}, + "dtmf": { + "digits": "digits_value", + "finish_digit": "finish_digit_value", + }, + "language_code": "language_code_value", + }, + "injected_parameters": {"fields": {}}, + "is_webhook_enabled": True, + "enable_sentiment_analysis": True, + }, + "virtual_agent_output": { + "session_parameters": {}, + "differences": [{"type_": 1, "description": "description_value"}], + "diagnostic_info": {}, + "triggered_intent": { + "name": "name_value", + "display_name": "display_name_value", + "training_phrases": [ + { + "id": "id_value", + "parts": [ + { + "text": "text_value", + "parameter_id": "parameter_id_value", + } + ], + "repeat_count": 1289, + } + ], + "parameters": [ + { + "id": "id_value", + "entity_type": "entity_type_value", + "is_list": True, + "redact": True, + } + ], + "priority": 898, + "is_fallback": True, + "labels": {}, + "description": "description_value", + }, + "current_page": { + "name": "name_value", + "display_name": "display_name_value", + "entry_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "form": { + "parameters": [ + { + "display_name": "display_name_value", + "required": True, + "entity_type": "entity_type_value", + "is_list": True, + "fill_behavior": { + "initial_prompt_fulfillment": {}, + "reprompt_event_handlers": [ + { + "name": "name_value", + "event": "event_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + }, + "default_value": {}, + "redact": True, + } + ] + }, + "transition_route_groups": [ + "transition_route_groups_value1", + "transition_route_groups_value2", + ], + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": {}, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + "event_handlers": {}, + }, + "text_responses": {}, + "status": { + "code": 411, + "message": "message_value", + "details": [ + { + "type_url": "type.googleapis.com/google.protobuf.Duration", + "value": b"\x08\x0c\x10\xdb\x07", + } + ], + }, + }, + } + ], + "creation_time": {"seconds": 751, "nanos": 543}, + "last_test_result": { + "name": "name_value", + "environment": "environment_value", + "conversation_turns": {}, + "test_result": 1, + "test_time": {}, + }, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_test_case(request) + + +def test_update_test_case_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_test_case.TestCase() + + # get arguments that satisfy an http rule for this method + sample_request = { + "test_case": { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + test_case=gcdc_test_case.TestCase(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_test_case.TestCase.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_test_case(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{test_case.name=projects/*/locations/*/agents/*/testCases/*}" + % client.transport._host, + args[1], + ) + + +def test_update_test_case_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_test_case( + gcdc_test_case.UpdateTestCaseRequest(), + test_case=gcdc_test_case.TestCase(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.RunTestCaseRequest, + dict, + ], +) +def test_run_test_case_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.run_test_case(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_run_test_case_rest_required_fields(request_type=test_case.RunTestCaseRequest): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).run_test_case._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.run_test_case(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_run_test_case_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.run_test_case._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_run_test_case_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_run_test_case" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_run_test_case" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.RunTestCaseRequest.pb(test_case.RunTestCaseRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.RunTestCaseRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.run_test_case( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_run_test_case_rest_bad_request( + transport: str = "rest", request_type=test_case.RunTestCaseRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.run_test_case(request) + + +def test_run_test_case_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.BatchRunTestCasesRequest, + dict, + ], +) +def test_batch_run_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.batch_run_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_batch_run_test_cases_rest_required_fields( + request_type=test_case.BatchRunTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request_init["test_cases"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_run_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + jsonified_request["testCases"] = "test_cases_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).batch_run_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + assert "testCases" in jsonified_request + assert jsonified_request["testCases"] == "test_cases_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.batch_run_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_batch_run_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.batch_run_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "testCases", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_batch_run_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_batch_run_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_batch_run_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.BatchRunTestCasesRequest.pb( + test_case.BatchRunTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.BatchRunTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.batch_run_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_batch_run_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.BatchRunTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.batch_run_test_cases(request) + + +def test_batch_run_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.CalculateCoverageRequest, + dict, + ], +) +def test_calculate_coverage_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"agent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.CalculateCoverageResponse( + agent="agent_value", + intent_coverage=test_case.IntentCoverage( + intents=[test_case.IntentCoverage.Intent(intent="intent_value")] + ), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.CalculateCoverageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.calculate_coverage(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.CalculateCoverageResponse) + assert response.agent == "agent_value" + + +def test_calculate_coverage_rest_required_fields( + request_type=test_case.CalculateCoverageRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["agent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).calculate_coverage._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["agent"] = "agent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).calculate_coverage._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("type_",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "agent" in jsonified_request + assert jsonified_request["agent"] == "agent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.CalculateCoverageResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.CalculateCoverageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.calculate_coverage(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_calculate_coverage_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.calculate_coverage._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("type",)) + & set( + ( + "agent", + "type", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_calculate_coverage_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_calculate_coverage" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_calculate_coverage" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.CalculateCoverageRequest.pb( + test_case.CalculateCoverageRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.CalculateCoverageResponse.to_json( + test_case.CalculateCoverageResponse() + ) + + request = test_case.CalculateCoverageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.CalculateCoverageResponse() + + client.calculate_coverage( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_calculate_coverage_rest_bad_request( + transport: str = "rest", request_type=test_case.CalculateCoverageRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"agent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.calculate_coverage(request) + + +def test_calculate_coverage_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ImportTestCasesRequest, + dict, + ], +) +def test_import_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.import_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_import_test_cases_rest_required_fields( + request_type=test_case.ImportTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).import_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.import_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_import_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.import_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_import_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_import_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_import_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ImportTestCasesRequest.pb( + test_case.ImportTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.ImportTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.import_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_import_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ImportTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.import_test_cases(request) + + +def test_import_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ExportTestCasesRequest, + dict, + ], +) +def test_export_test_cases_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.export_test_cases(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_export_test_cases_rest_required_fields( + request_type=test_case.ExportTestCasesRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).export_test_cases._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.export_test_cases(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_export_test_cases_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.export_test_cases._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_export_test_cases_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.TestCasesRestInterceptor, "post_export_test_cases" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_export_test_cases" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ExportTestCasesRequest.pb( + test_case.ExportTestCasesRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = test_case.ExportTestCasesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.export_test_cases( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_export_test_cases_rest_bad_request( + transport: str = "rest", request_type=test_case.ExportTestCasesRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.export_test_cases(request) + + +def test_export_test_cases_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.ListTestCaseResultsRequest, + dict, + ], +) +def test_list_test_case_results_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_test_case_results(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTestCaseResultsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_test_case_results_rest_required_fields( + request_type=test_case.ListTestCaseResultsRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_case_results._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_test_case_results._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "filter", + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_test_case_results(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_test_case_results_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_test_case_results._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "filter", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_test_case_results_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_list_test_case_results" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_list_test_case_results" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.ListTestCaseResultsRequest.pb( + test_case.ListTestCaseResultsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.ListTestCaseResultsResponse.to_json( + test_case.ListTestCaseResultsResponse() + ) + + request = test_case.ListTestCaseResultsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.ListTestCaseResultsResponse() + + client.list_test_case_results( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_test_case_results_rest_bad_request( + transport: str = "rest", request_type=test_case.ListTestCaseResultsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_test_case_results(request) + + +def test_list_test_case_results_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.ListTestCaseResultsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.ListTestCaseResultsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_test_case_results(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/testCases/*}/results" + % client.transport._host, + args[1], + ) + + +def test_list_test_case_results_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_test_case_results( + test_case.ListTestCaseResultsRequest(), + parent="parent_value", + ) + + +def test_list_test_case_results_rest_pager(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + test_case.TestCaseResult(), + test_case.TestCaseResult(), + ], + next_page_token="abc", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[], + next_page_token="def", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + ], + next_page_token="ghi", + ), + test_case.ListTestCaseResultsResponse( + test_case_results=[ + test_case.TestCaseResult(), + test_case.TestCaseResult(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + test_case.ListTestCaseResultsResponse.to_json(x) for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4" + } + + pager = client.list_test_case_results(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, test_case.TestCaseResult) for i in results) + + pages = list(client.list_test_case_results(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + test_case.GetTestCaseResultRequest, + dict, + ], +) +def test_get_test_case_result_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult( + name="name_value", + environment="environment_value", + test_result=test_case.TestResult.PASSED, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_test_case_result(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, test_case.TestCaseResult) + assert response.name == "name_value" + assert response.environment == "environment_value" + assert response.test_result == test_case.TestResult.PASSED + + +def test_get_test_case_result_rest_required_fields( + request_type=test_case.GetTestCaseResultRequest, +): + transport_class = transports.TestCasesRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_test_case_result._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_test_case_result(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_test_case_result_rest_unset_required_fields(): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_test_case_result._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_test_case_result_rest_interceptors(null_interceptor): + transport = transports.TestCasesRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.TestCasesRestInterceptor(), + ) + client = TestCasesClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TestCasesRestInterceptor, "post_get_test_case_result" + ) as post, mock.patch.object( + transports.TestCasesRestInterceptor, "pre_get_test_case_result" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = test_case.GetTestCaseResultRequest.pb( + test_case.GetTestCaseResultRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = test_case.TestCaseResult.to_json( + test_case.TestCaseResult() + ) + + request = test_case.GetTestCaseResultRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = test_case.TestCaseResult() + + client.get_test_case_result( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_test_case_result_rest_bad_request( + transport: str = "rest", request_type=test_case.GetTestCaseResultRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_test_case_result(request) + + +def test_get_test_case_result_rest_flattened(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = test_case.TestCaseResult() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/testCases/sample4/results/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = test_case.TestCaseResult.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_test_case_result(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/testCases/*/results/*}" + % client.transport._host, + args[1], + ) + + +def test_get_test_case_result_rest_flattened_error(transport: str = "rest"): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_test_case_result( + test_case.GetTestCaseResultRequest(), + name="name_value", + ) + + +def test_get_test_case_result_rest_error(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TestCasesClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TestCasesClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TestCasesClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = TestCasesClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TestCasesGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TestCasesGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TestCasesGrpcTransport, + transports.TestCasesGrpcAsyncIOTransport, + transports.TestCasesRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = TestCasesClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.TestCasesGrpcTransport, + ) + + +def test_test_cases_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.TestCasesTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_test_cases_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.test_cases.transports.TestCasesTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TestCasesTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_test_cases", + "batch_delete_test_cases", + "get_test_case", + "create_test_case", + "update_test_case", + "run_test_case", "batch_run_test_cases", "calculate_coverage", "import_test_cases", @@ -3797,6 +7785,7 @@ def test_test_cases_transport_auth_adc(transport_class): [ transports.TestCasesGrpcTransport, transports.TestCasesGrpcAsyncIOTransport, + transports.TestCasesRestTransport, ], ) def test_test_cases_transport_auth_gdch_credentials(transport_class): @@ -3894,11 +7883,40 @@ def test_test_cases_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_test_cases_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.TestCasesRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_test_cases_rest_lro_client(): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_test_cases_host_no_port(transport_name): @@ -3909,7 +7927,11 @@ def test_test_cases_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -3917,6 +7939,7 @@ def test_test_cases_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_test_cases_host_with_port(transport_name): @@ -3927,7 +7950,66 @@ def test_test_cases_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_test_cases_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = TestCasesClient( + credentials=creds1, + transport=transport_name, + ) + client2 = TestCasesClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_test_cases._session + session2 = client2.transport.list_test_cases._session + assert session1 != session2 + session1 = client1.transport.batch_delete_test_cases._session + session2 = client2.transport.batch_delete_test_cases._session + assert session1 != session2 + session1 = client1.transport.get_test_case._session + session2 = client2.transport.get_test_case._session + assert session1 != session2 + session1 = client1.transport.create_test_case._session + session2 = client2.transport.create_test_case._session + assert session1 != session2 + session1 = client1.transport.update_test_case._session + session2 = client2.transport.update_test_case._session + assert session1 != session2 + session1 = client1.transport.run_test_case._session + session2 = client2.transport.run_test_case._session + assert session1 != session2 + session1 = client1.transport.batch_run_test_cases._session + session2 = client2.transport.batch_run_test_cases._session + assert session1 != session2 + session1 = client1.transport.calculate_coverage._session + session2 = client2.transport.calculate_coverage._session + assert session1 != session2 + session1 = client1.transport.import_test_cases._session + session2 = client2.transport.import_test_cases._session + assert session1 != session2 + session1 = client1.transport.export_test_cases._session + session2 = client2.transport.export_test_cases._session + assert session1 != session2 + session1 = client1.transport.list_test_case_results._session + session2 = client2.transport.list_test_case_results._session + assert session1 != session2 + session1 = client1.transport.get_test_case_result._session + session2 = client2.transport.get_test_case_result._session + assert session1 != session2 def test_test_cases_grpc_transport_channel(): @@ -4524,6 +8606,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = TestCasesClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = TestCasesClient( credentials=ga_credentials.AnonymousCredentials(), @@ -5241,6 +9609,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -5258,6 +9627,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_transition_route_groups.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_transition_route_groups.py index 6e6b3dbf..0abc8f46 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_transition_route_groups.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_transition_route_groups.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -112,6 +119,7 @@ def test__get_default_mtls_endpoint(): [ (TransitionRouteGroupsClient, "grpc"), (TransitionRouteGroupsAsyncClient, "grpc_asyncio"), + (TransitionRouteGroupsClient, "rest"), ], ) def test_transition_route_groups_client_from_service_account_info( @@ -127,7 +135,11 @@ def test_transition_route_groups_client_from_service_account_info( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -135,6 +147,7 @@ def test_transition_route_groups_client_from_service_account_info( [ (transports.TransitionRouteGroupsGrpcTransport, "grpc"), (transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.TransitionRouteGroupsRestTransport, "rest"), ], ) def test_transition_route_groups_client_service_account_always_use_jwt( @@ -160,6 +173,7 @@ def test_transition_route_groups_client_service_account_always_use_jwt( [ (TransitionRouteGroupsClient, "grpc"), (TransitionRouteGroupsAsyncClient, "grpc_asyncio"), + (TransitionRouteGroupsClient, "rest"), ], ) def test_transition_route_groups_client_from_service_account_file( @@ -182,13 +196,18 @@ def test_transition_route_groups_client_from_service_account_file( assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_transition_route_groups_client_get_transport_class(): transport = TransitionRouteGroupsClient.get_transport_class() available_transports = [ transports.TransitionRouteGroupsGrpcTransport, + transports.TransitionRouteGroupsRestTransport, ] assert transport in available_transports @@ -209,6 +228,11 @@ def test_transition_route_groups_client_get_transport_class(): transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + ), ], ) @mock.patch.object( @@ -364,6 +388,18 @@ def test_transition_route_groups_client_client_options( "grpc_asyncio", "false", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + "true", + ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + "false", + ), ], ) @mock.patch.object( @@ -567,6 +603,11 @@ def test_transition_route_groups_client_get_mtls_endpoint_and_cert_source(client transports.TransitionRouteGroupsGrpcAsyncIOTransport, "grpc_asyncio", ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + ), ], ) def test_transition_route_groups_client_client_options_scopes( @@ -607,6 +648,12 @@ def test_transition_route_groups_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + ( + TransitionRouteGroupsClient, + transports.TransitionRouteGroupsRestTransport, + "rest", + None, + ), ], ) def test_transition_route_groups_client_client_options_credentials_file( @@ -2206,208 +2253,2050 @@ async def test_delete_transition_route_group_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.ListTransitionRouteGroupsRequest, + dict, + ], +) +def test_list_transition_route_groups_rest(request_type): + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, - ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) - # It is an error to provide an api_key and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options=options, - transport=transport, + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) - # It is an error to provide scopes and a transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_transition_route_groups(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTransitionRouteGroupsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_transition_route_groups_rest_required_fields( + request_type=transition_route_group.ListTransitionRouteGroupsRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - with pytest.raises(ValueError): - client = TransitionRouteGroupsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_transition_route_groups._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_transition_route_groups._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "page_size", + "page_token", ) + ) + jsonified_request.update(unset_fields) + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = TransitionRouteGroupsClient(transport=transport) - assert client.transport is transport + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + response_value = Response() + response_value.status_code = 200 -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.TransitionRouteGroupsGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), + pb_return_value = ( + transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value + ) + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_transition_route_groups(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_transition_route_groups_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - channel = transport.grpc_channel - assert channel - transport = transports.TransitionRouteGroupsGrpcAsyncIOTransport( + unset_fields = transport.list_transition_route_groups._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_transition_route_groups_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), ) - channel = transport.grpc_channel - assert channel + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_list_transition_route_groups", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_list_transition_route_groups", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = transition_route_group.ListTransitionRouteGroupsRequest.pb( + transition_route_group.ListTransitionRouteGroupsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + transition_route_group.ListTransitionRouteGroupsResponse.to_json( + transition_route_group.ListTransitionRouteGroupsResponse() + ) + ) + request = transition_route_group.ListTransitionRouteGroupsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = transition_route_group.ListTransitionRouteGroupsResponse() -@pytest.mark.parametrize( - "transport_class", - [ - transports.TransitionRouteGroupsGrpcTransport, - transports.TransitionRouteGroupsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + client.list_transition_route_groups( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + pre.assert_called_once() + post.assert_called_once() -@pytest.mark.parametrize( - "transport_name", - [ - "grpc", - ], -) -def test_transport_kind(transport_name): - transport = TransitionRouteGroupsClient.get_transport_class(transport_name)( + +def test_list_transition_route_groups_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.ListTransitionRouteGroupsRequest, +): + client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_transition_route_groups(request) -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. + +def test_list_transition_route_groups_rest_flattened(): client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.TransitionRouteGroupsGrpcTransport, + transport="rest", ) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.ListTransitionRouteGroupsResponse() -def test_transition_route_groups_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.TransitionRouteGroupsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) -def test_transition_route_groups_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.TransitionRouteGroupsTransport( - credentials=ga_credentials.AnonymousCredentials(), + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.ListTransitionRouteGroupsResponse.pb( + return_value ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_transition_route_groups", - "get_transition_route_group", - "create_transition_route_group", - "update_transition_route_group", - "delete_transition_route_group", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", - ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) + client.list_transition_route_groups(**mock_args) - with pytest.raises(NotImplementedError): - transport.close() + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups" + % client.transport._host, + args[1], + ) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() +def test_list_transition_route_groups_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_transition_route_groups_base_transport_with_credentials_file(): - # Instantiate the base transport with a credentials file - with mock.patch.object( - google.auth, "load_credentials_from_file", autospec=True - ) as load_creds, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.TransitionRouteGroupsTransport( - credentials_file="credentials.json", - quota_project_id="octopus", - ) - load_creds.assert_called_once_with( - "credentials.json", - scopes=None, - default_scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/dialogflow", - ), - quota_project_id="octopus", + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_transition_route_groups( + transition_route_group.ListTransitionRouteGroupsRequest(), + parent="parent_value", ) -def test_transition_route_groups_base_transport_with_adc(): - # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.TransitionRouteGroupsTransport() - adc.assert_called_once() - +def test_list_transition_route_groups_rest_pager(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) -def test_transition_route_groups_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(google.auth, "default", autospec=True) as adc: + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + ], + next_page_token="abc", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[], + next_page_token="def", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + ], + next_page_token="ghi", + ), + transition_route_group.ListTransitionRouteGroupsResponse( + transition_route_groups=[ + transition_route_group.TransitionRouteGroup(), + transition_route_group.TransitionRouteGroup(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + transition_route_group.ListTransitionRouteGroupsResponse.to_json(x) + for x in response + ) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_transition_route_groups(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, transition_route_group.TransitionRouteGroup) for i in results + ) + + pages = list(client.list_transition_route_groups(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.GetTransitionRouteGroupRequest, + dict, + ], +) +def test_get_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.TransitionRouteGroup.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_get_transition_route_group_rest_required_fields( + request_type=transition_route_group.GetTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_transition_route_group._get_unset_required_fields({}) + assert set(unset_fields) == (set(("languageCode",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_get_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_get_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = transition_route_group.GetTransitionRouteGroupRequest.pb( + transition_route_group.GetTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = transition_route_group.TransitionRouteGroup.to_json( + transition_route_group.TransitionRouteGroup() + ) + + request = transition_route_group.GetTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = transition_route_group.TransitionRouteGroup() + + client.get_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.GetTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_transition_route_group(request) + + +def test_get_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = transition_route_group.TransitionRouteGroup.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_get_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_transition_route_group( + transition_route_group.GetTransitionRouteGroupRequest(), + name="name_value", + ) + + +def test_get_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_transition_route_group.CreateTransitionRouteGroupRequest, + dict, + ], +) +def test_create_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["transition_route_group"] = { + "name": "name_value", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_create_transition_route_group_rest_required_fields( + request_type=gcdc_transition_route_group.CreateTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("language_code",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == ( + set(("languageCode",)) + & set( + ( + "parent", + "transitionRouteGroup", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_create_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_create_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_transition_route_group.CreateTransitionRouteGroupRequest.pb( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + gcdc_transition_route_group.TransitionRouteGroup.to_json( + gcdc_transition_route_group.TransitionRouteGroup() + ) + ) + + request = gcdc_transition_route_group.CreateTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_transition_route_group.TransitionRouteGroup() + + client.create_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=gcdc_transition_route_group.CreateTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["transition_route_group"] = { + "name": "name_value", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_transition_route_group(request) + + +def test_create_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/transitionRouteGroups" + % client.transport._host, + args[1], + ) + + +def test_create_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_transition_route_group( + gcdc_transition_route_group.CreateTransitionRouteGroupRequest(), + parent="parent_value", + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + ) + + +def test_create_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, + dict, + ], +) +def test_update_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + request_init["transition_route_group"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup( + name="name_value", + display_name="display_name_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_transition_route_group.TransitionRouteGroup) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + + +def test_update_transition_route_group_rest_required_fields( + request_type=gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "language_code", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == ( + set( + ( + "languageCode", + "updateMask", + ) + ) + & set(("transitionRouteGroup",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "post_update_transition_route_group", + ) as post, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_update_transition_route_group", + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_transition_route_group.UpdateTransitionRouteGroupRequest.pb( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = ( + gcdc_transition_route_group.TransitionRouteGroup.to_json( + gcdc_transition_route_group.TransitionRouteGroup() + ) + ) + + request = gcdc_transition_route_group.UpdateTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_transition_route_group.TransitionRouteGroup() + + client.update_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=gcdc_transition_route_group.UpdateTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + request_init["transition_route_group"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5", + "display_name": "display_name_value", + "transition_routes": [ + { + "name": "name_value", + "intent": "intent_value", + "condition": "condition_value", + "trigger_fulfillment": { + "messages": [ + { + "text": { + "text": ["text_value1", "text_value2"], + "allow_playback_interruption": True, + }, + "payload": {"fields": {}}, + "conversation_success": {"metadata": {}}, + "output_audio_text": { + "text": "text_value", + "ssml": "ssml_value", + "allow_playback_interruption": True, + }, + "live_agent_handoff": {"metadata": {}}, + "end_interaction": {}, + "play_audio": { + "audio_uri": "audio_uri_value", + "allow_playback_interruption": True, + }, + "mixed_audio": { + "segments": [ + { + "audio": b"audio_blob", + "uri": "uri_value", + "allow_playback_interruption": True, + } + ] + }, + "telephony_transfer_call": { + "phone_number": "phone_number_value" + }, + "channel": "channel_value", + } + ], + "webhook": "webhook_value", + "return_partial_responses": True, + "tag": "tag_value", + "set_parameter_actions": [ + { + "parameter": "parameter_value", + "value": { + "null_value": 0, + "number_value": 0.1285, + "string_value": "string_value_value", + "bool_value": True, + "struct_value": {}, + "list_value": {"values": {}}, + }, + } + ], + "conditional_cases": [ + { + "cases": [ + { + "condition": "condition_value", + "case_content": [ + {"message": {}, "additional_cases": {}} + ], + } + ] + } + ], + }, + "target_page": "target_page_value", + "target_flow": "target_flow_value", + } + ], + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_transition_route_group(request) + + +def test_update_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_transition_route_group.TransitionRouteGroup() + + # get arguments that satisfy an http rule for this method + sample_request = { + "transition_route_group": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_transition_route_group.TransitionRouteGroup.pb( + return_value + ) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{transition_route_group.name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_update_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_transition_route_group( + gcdc_transition_route_group.UpdateTransitionRouteGroupRequest(), + transition_route_group=gcdc_transition_route_group.TransitionRouteGroup( + name="name_value" + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + transition_route_group.DeleteTransitionRouteGroupRequest, + dict, + ], +) +def test_delete_transition_route_group_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_transition_route_group(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_transition_route_group_rest_required_fields( + request_type=transition_route_group.DeleteTransitionRouteGroupRequest, +): + transport_class = transports.TransitionRouteGroupsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_transition_route_group._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_transition_route_group._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_transition_route_group(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_transition_route_group_rest_unset_required_fields(): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_transition_route_group._get_unset_required_fields( + {} + ) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_transition_route_group_rest_interceptors(null_interceptor): + transport = transports.TransitionRouteGroupsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.TransitionRouteGroupsRestInterceptor(), + ) + client = TransitionRouteGroupsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.TransitionRouteGroupsRestInterceptor, + "pre_delete_transition_route_group", + ) as pre: + pre.assert_not_called() + pb_message = transition_route_group.DeleteTransitionRouteGroupRequest.pb( + transition_route_group.DeleteTransitionRouteGroupRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = transition_route_group.DeleteTransitionRouteGroupRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_transition_route_group( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_transition_route_group_rest_bad_request( + transport: str = "rest", + request_type=transition_route_group.DeleteTransitionRouteGroupRequest, +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_transition_route_group(request) + + +def test_delete_transition_route_group_rest_flattened(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/transitionRouteGroups/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_transition_route_group(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/transitionRouteGroups/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_transition_route_group_rest_flattened_error(transport: str = "rest"): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_transition_route_group( + transition_route_group.DeleteTransitionRouteGroupRequest(), + name="name_value", + ) + + +def test_delete_transition_route_group_rest_error(): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TransitionRouteGroupsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = TransitionRouteGroupsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TransitionRouteGroupsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TransitionRouteGroupsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TransitionRouteGroupsGrpcTransport, + transports.TransitionRouteGroupsGrpcAsyncIOTransport, + transports.TransitionRouteGroupsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = TransitionRouteGroupsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.TransitionRouteGroupsGrpcTransport, + ) + + +def test_transition_route_groups_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.TransitionRouteGroupsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_transition_route_groups_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TransitionRouteGroupsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_transition_route_groups", + "get_transition_route_group", + "create_transition_route_group", + "update_transition_route_group", + "delete_transition_route_group", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_transition_route_groups_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.TransitionRouteGroupsTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/dialogflow", + ), + quota_project_id="octopus", + ) + + +def test_transition_route_groups_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.transition_route_groups.transports.TransitionRouteGroupsTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.TransitionRouteGroupsTransport() + adc.assert_called_once() + + +def test_transition_route_groups_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: adc.return_value = (ga_credentials.AnonymousCredentials(), None) TransitionRouteGroupsClient() adc.assert_called_once_with( @@ -2448,6 +4337,7 @@ def test_transition_route_groups_transport_auth_adc(transport_class): [ transports.TransitionRouteGroupsGrpcTransport, transports.TransitionRouteGroupsGrpcAsyncIOTransport, + transports.TransitionRouteGroupsRestTransport, ], ) def test_transition_route_groups_transport_auth_gdch_credentials(transport_class): @@ -2552,11 +4442,23 @@ def test_transition_route_groups_grpc_transport_client_cert_source_for_mtls( ) +def test_transition_route_groups_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.TransitionRouteGroupsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_transition_route_groups_host_no_port(transport_name): @@ -2567,7 +4469,11 @@ def test_transition_route_groups_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2575,6 +4481,7 @@ def test_transition_route_groups_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_transition_route_groups_host_with_port(transport_name): @@ -2585,7 +4492,45 @@ def test_transition_route_groups_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_transition_route_groups_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = TransitionRouteGroupsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = TransitionRouteGroupsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_transition_route_groups._session + session2 = client2.transport.list_transition_route_groups._session + assert session1 != session2 + session1 = client1.transport.get_transition_route_group._session + session2 = client2.transport.get_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.create_transition_route_group._session + session2 = client2.transport.create_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.update_transition_route_group._session + session2 = client2.transport.update_transition_route_group._session + assert session1 != session2 + session1 = client1.transport.delete_transition_route_group._session + session2 = client2.transport.delete_transition_route_group._session + assert session1 != session2 def test_transition_route_groups_grpc_transport_channel(): @@ -3009,6 +4954,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = TransitionRouteGroupsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = TransitionRouteGroupsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3730,6 +5961,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3747,6 +5979,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_versions.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_versions.py index f381fdc7..cd23e8ee 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_versions.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_versions.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -102,6 +109,7 @@ def test__get_default_mtls_endpoint(): [ (VersionsClient, "grpc"), (VersionsAsyncClient, "grpc_asyncio"), + (VersionsClient, "rest"), ], ) def test_versions_client_from_service_account_info(client_class, transport_name): @@ -115,7 +123,11 @@ def test_versions_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -123,6 +135,7 @@ def test_versions_client_from_service_account_info(client_class, transport_name) [ (transports.VersionsGrpcTransport, "grpc"), (transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.VersionsRestTransport, "rest"), ], ) def test_versions_client_service_account_always_use_jwt( @@ -148,6 +161,7 @@ def test_versions_client_service_account_always_use_jwt( [ (VersionsClient, "grpc"), (VersionsAsyncClient, "grpc_asyncio"), + (VersionsClient, "rest"), ], ) def test_versions_client_from_service_account_file(client_class, transport_name): @@ -168,13 +182,18 @@ def test_versions_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_versions_client_get_transport_class(): transport = VersionsClient.get_transport_class() available_transports = [ transports.VersionsGrpcTransport, + transports.VersionsRestTransport, ] assert transport in available_transports @@ -187,6 +206,7 @@ def test_versions_client_get_transport_class(): [ (VersionsClient, transports.VersionsGrpcTransport, "grpc"), (VersionsAsyncClient, transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (VersionsClient, transports.VersionsRestTransport, "rest"), ], ) @mock.patch.object( @@ -328,6 +348,8 @@ def test_versions_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (VersionsClient, transports.VersionsRestTransport, "rest", "true"), + (VersionsClient, transports.VersionsRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -517,6 +539,7 @@ def test_versions_client_get_mtls_endpoint_and_cert_source(client_class): [ (VersionsClient, transports.VersionsGrpcTransport, "grpc"), (VersionsAsyncClient, transports.VersionsGrpcAsyncIOTransport, "grpc_asyncio"), + (VersionsClient, transports.VersionsRestTransport, "rest"), ], ) def test_versions_client_client_options_scopes( @@ -552,6 +575,7 @@ def test_versions_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (VersionsClient, transports.VersionsRestTransport, "rest", None), ], ) def test_versions_client_client_options_credentials_file( @@ -2492,141 +2516,2173 @@ async def test_compare_versions_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.VersionsGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + version.ListVersionsRequest, + dict, + ], +) +def test_list_versions_rest(request_type): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = VersionsClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.VersionsGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_versions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListVersionsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_versions_rest_required_fields(request_type=version.ListVersionsRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_versions._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = VersionsClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_versions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_versions_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_versions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.VersionsGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_versions_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = VersionsClient( - client_options=options, - transport=transport, + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_list_versions" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_list_versions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.ListVersionsRequest.pb(version.ListVersionsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.ListVersionsResponse.to_json( + version.ListVersionsResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = VersionsClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = version.ListVersionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.ListVersionsResponse() + + client.list_versions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.VersionsGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_versions_rest_bad_request( + transport: str = "rest", request_type=version.ListVersionsRequest +): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = VersionsClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.VersionsGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_versions(request) + + +def test_list_versions_rest_flattened(): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = VersionsClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.ListVersionsResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.VersionsGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.ListVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_versions(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/versions" + % client.transport._host, + args[1], + ) + + +def test_list_versions_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.VersionsGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_versions( + version.ListVersionsRequest(), + parent="parent_value", + ) + + +def test_list_versions_rest_pager(transport: str = "rest"): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + version.ListVersionsResponse( + versions=[ + version.Version(), + version.Version(), + version.Version(), + ], + next_page_token="abc", + ), + version.ListVersionsResponse( + versions=[], + next_page_token="def", + ), + version.ListVersionsResponse( + versions=[ + version.Version(), + ], + next_page_token="ghi", + ), + version.ListVersionsResponse( + versions=[ + version.Version(), + version.Version(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(version.ListVersionsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.VersionsGrpcTransport, - transports.VersionsGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + pager = client.list_versions(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, version.Version) for i in results) + + pages = list(client.list_versions(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + version.GetVersionRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = VersionsClient.get_transport_class(transport_name)( +def test_get_version_rest(request_type): + client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.Version( + name="name_value", + display_name="display_name_value", + description="description_value", + state=version.Version.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_version(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, version.Version) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == version.Version.State.RUNNING + + +def test_get_version_rest_required_fields(request_type=version.GetVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert isinstance( - client.transport, - transports.VersionsGrpcTransport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.Version() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials ) + unset_fields = transport.get_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) -def test_versions_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.VersionsTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", - ) +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_get_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_get_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.GetVersionRequest.pb(version.GetVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.Version.to_json(version.Version()) + + request = version.GetVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.Version() -def test_versions_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.versions.transports.VersionsTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.VersionsTransport( - credentials=ga_credentials.AnonymousCredentials(), + client.get_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # Every method on the transport should just blindly + pre.assert_called_once() + post.assert_called_once() + + +def test_get_version_rest_bad_request( + transport: str = "rest", request_type=version.GetVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_version(request) + + +def test_get_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.Version() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_get_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_version( + version.GetVersionRequest(), + name="name_value", + ) + + +def test_get_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_version.CreateVersionRequest, + dict, + ], +) +def test_create_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["version"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_version(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_create_version_rest_required_fields( + request_type=gcdc_version.CreateVersionRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_version._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "version", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.VersionsRestInterceptor, "post_create_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_create_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_version.CreateVersionRequest.pb( + gcdc_version.CreateVersionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = gcdc_version.CreateVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.create_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_version_rest_bad_request( + transport: str = "rest", request_type=gcdc_version.CreateVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + request_init["version"] = { + "name": "name_value", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_version(request) + + +def test_create_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "parent": "projects/sample1/locations/sample2/agents/sample3/flows/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + version=gcdc_version.Version(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*/flows/*}/versions" + % client.transport._host, + args[1], + ) + + +def test_create_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_version( + gcdc_version.CreateVersionRequest(), + parent="parent_value", + version=gcdc_version.Version(name="name_value"), + ) + + +def test_create_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_version.UpdateVersionRequest, + dict, + ], +) +def test_update_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + request_init["version"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version( + name="name_value", + display_name="display_name_value", + description="description_value", + state=gcdc_version.Version.State.RUNNING, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_version(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_version.Version) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.description == "description_value" + assert response.state == gcdc_version.Version.State.RUNNING + + +def test_update_version_rest_required_fields( + request_type=gcdc_version.UpdateVersionRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_version._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_version._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("updateMask",)) + & set( + ( + "version", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_update_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_update_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_version.UpdateVersionRequest.pb( + gcdc_version.UpdateVersionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_version.Version.to_json(gcdc_version.Version()) + + request = gcdc_version.UpdateVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_version.Version() + + client.update_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_version_rest_bad_request( + transport: str = "rest", request_type=gcdc_version.UpdateVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + request_init["version"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5", + "display_name": "display_name_value", + "description": "description_value", + "nlu_settings": { + "model_type": 1, + "classification_threshold": 0.25520000000000004, + "model_training_mode": 1, + }, + "create_time": {"seconds": 751, "nanos": 543}, + "state": 1, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_version(request) + + +def test_update_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_version.Version() + + # get arguments that satisfy an http rule for this method + sample_request = { + "version": { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + } + + # get truthy value for each flattened field + mock_args = dict( + version=gcdc_version.Version(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_version.Version.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{version.name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_update_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_version( + gcdc_version.UpdateVersionRequest(), + version=gcdc_version.Version(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.DeleteVersionRequest, + dict, + ], +) +def test_delete_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_version(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_version_rest_required_fields(request_type=version.DeleteVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "pre_delete_version" + ) as pre: + pre.assert_not_called() + pb_message = version.DeleteVersionRequest.pb(version.DeleteVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = version.DeleteVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_version_rest_bad_request( + transport: str = "rest", request_type=version.DeleteVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_version(request) + + +def test_delete_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_version( + version.DeleteVersionRequest(), + name="name_value", + ) + + +def test_delete_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.LoadVersionRequest, + dict, + ], +) +def test_load_version_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.load_version(request) + + # Establish that the response is the type that we expect. + assert response.operation.name == "operations/spam" + + +def test_load_version_rest_required_fields(request_type=version.LoadVersionRequest): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).load_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).load_version._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.load_version(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_load_version_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.load_version._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_load_version_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + operation.Operation, "_set_result_from_operation" + ), mock.patch.object( + transports.VersionsRestInterceptor, "post_load_version" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_load_version" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.LoadVersionRequest.pb(version.LoadVersionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = json_format.MessageToJson( + operations_pb2.Operation() + ) + + request = version.LoadVersionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + + client.load_version( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_load_version_rest_bad_request( + transport: str = "rest", request_type=version.LoadVersionRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.load_version(request) + + +def test_load_version_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.load_version(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/flows/*/versions/*}:load" + % client.transport._host, + args[1], + ) + + +def test_load_version_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.load_version( + version.LoadVersionRequest(), + name="name_value", + ) + + +def test_load_version_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + version.CompareVersionsRequest, + dict, + ], +) +def test_compare_versions_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse( + base_version_content_json="base_version_content_json_value", + target_version_content_json="target_version_content_json_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.compare_versions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, version.CompareVersionsResponse) + assert response.base_version_content_json == "base_version_content_json_value" + assert response.target_version_content_json == "target_version_content_json_value" + + +def test_compare_versions_rest_required_fields( + request_type=version.CompareVersionsRequest, +): + transport_class = transports.VersionsRestTransport + + request_init = {} + request_init["base_version"] = "" + request_init["target_version"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).compare_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["baseVersion"] = "base_version_value" + jsonified_request["targetVersion"] = "target_version_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).compare_versions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "baseVersion" in jsonified_request + assert jsonified_request["baseVersion"] == "base_version_value" + assert "targetVersion" in jsonified_request + assert jsonified_request["targetVersion"] == "target_version_value" + + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.compare_versions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_compare_versions_rest_unset_required_fields(): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.compare_versions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "baseVersion", + "targetVersion", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_compare_versions_rest_interceptors(null_interceptor): + transport = transports.VersionsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.VersionsRestInterceptor(), + ) + client = VersionsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.VersionsRestInterceptor, "post_compare_versions" + ) as post, mock.patch.object( + transports.VersionsRestInterceptor, "pre_compare_versions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = version.CompareVersionsRequest.pb(version.CompareVersionsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = version.CompareVersionsResponse.to_json( + version.CompareVersionsResponse() + ) + + request = version.CompareVersionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = version.CompareVersionsResponse() + + client.compare_versions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_compare_versions_rest_bad_request( + transport: str = "rest", request_type=version.CompareVersionsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.compare_versions(request) + + +def test_compare_versions_rest_flattened(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = version.CompareVersionsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = { + "base_version": "projects/sample1/locations/sample2/agents/sample3/flows/sample4/versions/sample5" + } + + # get truthy value for each flattened field + mock_args = dict( + base_version="base_version_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = version.CompareVersionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.compare_versions(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{base_version=projects/*/locations/*/agents/*/flows/*/versions/*}:compareVersions" + % client.transport._host, + args[1], + ) + + +def test_compare_versions_rest_flattened_error(transport: str = "rest"): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.compare_versions( + version.CompareVersionsRequest(), + base_version="base_version_value", + ) + + +def test_compare_versions_rest_error(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = VersionsClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = VersionsClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = VersionsClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = VersionsClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.VersionsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.VersionsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.VersionsGrpcTransport, + transports.VersionsGrpcAsyncIOTransport, + transports.VersionsRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = VersionsClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.VersionsGrpcTransport, + ) + + +def test_versions_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.VersionsTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_versions_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.versions.transports.VersionsTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.VersionsTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly # raise NotImplementedError. methods = ( "list_versions", @@ -2741,6 +4797,7 @@ def test_versions_transport_auth_adc(transport_class): [ transports.VersionsGrpcTransport, transports.VersionsGrpcAsyncIOTransport, + transports.VersionsRestTransport, ], ) def test_versions_transport_auth_gdch_credentials(transport_class): @@ -2838,11 +4895,40 @@ def test_versions_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_versions_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.VersionsRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +def test_versions_rest_lro_client(): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + transport = client.transport + + # Ensure that we have a api-core operations client. + assert isinstance( + transport.operations_client, + operations_v1.AbstractOperationsClient, + ) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_versions_host_no_port(transport_name): @@ -2853,7 +4939,11 @@ def test_versions_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2861,6 +4951,7 @@ def test_versions_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_versions_host_with_port(transport_name): @@ -2871,7 +4962,51 @@ def test_versions_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_versions_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = VersionsClient( + credentials=creds1, + transport=transport_name, + ) + client2 = VersionsClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_versions._session + session2 = client2.transport.list_versions._session + assert session1 != session2 + session1 = client1.transport.get_version._session + session2 = client2.transport.get_version._session + assert session1 != session2 + session1 = client1.transport.create_version._session + session2 = client2.transport.create_version._session + assert session1 != session2 + session1 = client1.transport.update_version._session + session2 = client2.transport.update_version._session + assert session1 != session2 + session1 = client1.transport.delete_version._session + session2 = client2.transport.delete_version._session + assert session1 != session2 + session1 = client1.transport.load_version._session + session2 = client2.transport.load_version._session + assert session1 != session2 + session1 = client1.transport.compare_versions._session + session2 = client2.transport.compare_versions._session + assert session1 != session2 def test_versions_grpc_transport_channel(): @@ -3198,6 +5333,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = VersionsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = VersionsClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3915,6 +6336,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3932,6 +6354,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/dialogflowcx_v3beta1/test_webhooks.py b/tests/unit/gapic/dialogflowcx_v3beta1/test_webhooks.py index b9c99b64..70b1fbd2 100644 --- a/tests/unit/gapic/dialogflowcx_v3beta1/test_webhooks.py +++ b/tests/unit/gapic/dialogflowcx_v3beta1/test_webhooks.py @@ -24,10 +24,17 @@ import grpc from grpc.experimental import aio +from collections.abc import Iterable +from google.protobuf import json_format +import json import math import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule from proto.marshal.rules import wrappers +from requests import Response +from requests import Request, PreparedRequest +from requests.sessions import Session +from google.protobuf import json_format from google.api_core import client_options from google.api_core import exceptions as core_exceptions @@ -95,6 +102,7 @@ def test__get_default_mtls_endpoint(): [ (WebhooksClient, "grpc"), (WebhooksAsyncClient, "grpc_asyncio"), + (WebhooksClient, "rest"), ], ) def test_webhooks_client_from_service_account_info(client_class, transport_name): @@ -108,7 +116,11 @@ def test_webhooks_client_from_service_account_info(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -116,6 +128,7 @@ def test_webhooks_client_from_service_account_info(client_class, transport_name) [ (transports.WebhooksGrpcTransport, "grpc"), (transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.WebhooksRestTransport, "rest"), ], ) def test_webhooks_client_service_account_always_use_jwt( @@ -141,6 +154,7 @@ def test_webhooks_client_service_account_always_use_jwt( [ (WebhooksClient, "grpc"), (WebhooksAsyncClient, "grpc_asyncio"), + (WebhooksClient, "rest"), ], ) def test_webhooks_client_from_service_account_file(client_class, transport_name): @@ -161,13 +175,18 @@ def test_webhooks_client_from_service_account_file(client_class, transport_name) assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) def test_webhooks_client_get_transport_class(): transport = WebhooksClient.get_transport_class() available_transports = [ transports.WebhooksGrpcTransport, + transports.WebhooksRestTransport, ] assert transport in available_transports @@ -180,6 +199,7 @@ def test_webhooks_client_get_transport_class(): [ (WebhooksClient, transports.WebhooksGrpcTransport, "grpc"), (WebhooksAsyncClient, transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (WebhooksClient, transports.WebhooksRestTransport, "rest"), ], ) @mock.patch.object( @@ -321,6 +341,8 @@ def test_webhooks_client_client_options(client_class, transport_class, transport "grpc_asyncio", "false", ), + (WebhooksClient, transports.WebhooksRestTransport, "rest", "true"), + (WebhooksClient, transports.WebhooksRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -510,6 +532,7 @@ def test_webhooks_client_get_mtls_endpoint_and_cert_source(client_class): [ (WebhooksClient, transports.WebhooksGrpcTransport, "grpc"), (WebhooksAsyncClient, transports.WebhooksGrpcAsyncIOTransport, "grpc_asyncio"), + (WebhooksClient, transports.WebhooksRestTransport, "rest"), ], ) def test_webhooks_client_client_options_scopes( @@ -545,6 +568,7 @@ def test_webhooks_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (WebhooksClient, transports.WebhooksRestTransport, "rest", None), ], ) def test_webhooks_client_client_options_credentials_file( @@ -2032,171 +2056,1648 @@ async def test_delete_webhook_flattened_error_async(): ) -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.WebhooksGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + webhook.ListWebhooksRequest, + dict, + ], +) +def test_list_webhooks_rest(request_type): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = WebhooksClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse( + next_page_token="next_page_token_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.WebhooksGrpcTransport( + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_webhooks(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListWebhooksPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_webhooks_rest_required_fields(request_type=webhook.ListWebhooksRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_webhooks._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_webhooks._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = WebhooksClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_webhooks(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_webhooks_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_webhooks._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) ) + & set(("parent",)) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.WebhooksGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_webhooks_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = WebhooksClient( - client_options=options, - transport=transport, + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_list_webhooks" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_list_webhooks" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = webhook.ListWebhooksRequest.pb(webhook.ListWebhooksRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = webhook.ListWebhooksResponse.to_json( + webhook.ListWebhooksResponse() ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = WebhooksClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + request = webhook.ListWebhooksRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = webhook.ListWebhooksResponse() + + client.list_webhooks( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) - # It is an error to provide scopes and a transport instance. - transport = transports.WebhooksGrpcTransport( + pre.assert_called_once() + post.assert_called_once() + + +def test_list_webhooks_rest_bad_request( + transport: str = "rest", request_type=webhook.ListWebhooksRequest +): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - with pytest.raises(ValueError): - client = WebhooksClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, - ) + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request = request_type(**request_init) -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.WebhooksGrpcTransport( + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_webhooks(request) + + +def test_list_webhooks_rest_flattened(): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - client = WebhooksClient(transport=transport) - assert client.transport is transport + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.ListWebhooksResponse() -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.WebhooksGrpcTransport( + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.ListWebhooksResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_webhooks(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/webhooks" + % client.transport._host, + args[1], + ) + + +def test_list_webhooks_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel - transport = transports.WebhooksGrpcAsyncIOTransport( + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_webhooks( + webhook.ListWebhooksRequest(), + parent="parent_value", + ) + + +def test_list_webhooks_rest_pager(transport: str = "rest"): + client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + webhook.Webhook(), + webhook.Webhook(), + ], + next_page_token="abc", + ), + webhook.ListWebhooksResponse( + webhooks=[], + next_page_token="def", + ), + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + ], + next_page_token="ghi", + ), + webhook.ListWebhooksResponse( + webhooks=[ + webhook.Webhook(), + webhook.Webhook(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(webhook.ListWebhooksResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values -@pytest.mark.parametrize( - "transport_class", - [ - transports.WebhooksGrpcTransport, - transports.WebhooksGrpcAsyncIOTransport, - ], -) -def test_transport_adc(transport_class): - # Test default credentials are used if not provided. - with mock.patch.object(google.auth, "default") as adc: - adc.return_value = (ga_credentials.AnonymousCredentials(), None) - transport_class() - adc.assert_called_once() + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + pager = client.list_webhooks(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, webhook.Webhook) for i in results) + + pages = list(client.list_webhooks(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + webhook.GetWebhookRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = WebhooksClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_webhook_rest(request_type): client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.WebhooksGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) -def test_webhooks_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.WebhooksTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=webhook.Webhook.GenericWebService(uri="uri_value"), ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_webhooks_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.cloud.dialogflowcx_v3beta1.services.webhooks.transports.WebhooksTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.WebhooksTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_webhook(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "list_webhooks", - "get_webhook", - "create_webhook", - "update_webhook", - "delete_webhook", - "get_location", - "list_locations", - "get_operation", - "cancel_operation", - "list_operations", + # Establish that the response is the type that we expect. + assert isinstance(response, webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_get_webhook_rest_required_fields(request_type=webhook.GetWebhookRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + # verify required fields with default values are now present -def test_webhooks_base_transport_with_credentials_file(): + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_get_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_get_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = webhook.GetWebhookRequest.pb(webhook.GetWebhookRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = webhook.Webhook.to_json(webhook.Webhook()) + + request = webhook.GetWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = webhook.Webhook() + + client.get_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_webhook_rest_bad_request( + transport: str = "rest", request_type=webhook.GetWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_webhook(request) + + +def test_get_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_get_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_webhook( + webhook.GetWebhookRequest(), + name="name_value", + ) + + +def test_get_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_webhook.CreateWebhookRequest, + dict, + ], +) +def test_create_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["webhook"] = { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=gcdc_webhook.Webhook.GenericWebService(uri="uri_value"), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_webhook(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_create_webhook_rest_required_fields( + request_type=gcdc_webhook.CreateWebhookRequest, +): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["parent"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_webhook._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "webhook", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_create_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_create_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_webhook.CreateWebhookRequest.pb( + gcdc_webhook.CreateWebhookRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_webhook.Webhook.to_json(gcdc_webhook.Webhook()) + + request = gcdc_webhook.CreateWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_webhook.Webhook() + + client.create_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_webhook_rest_bad_request( + transport: str = "rest", request_type=gcdc_webhook.CreateWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + request_init["webhook"] = { + "name": "name_value", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_webhook(request) + + +def test_create_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1/locations/sample2/agents/sample3"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + webhook=gcdc_webhook.Webhook(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{parent=projects/*/locations/*/agents/*}/webhooks" + % client.transport._host, + args[1], + ) + + +def test_create_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_webhook( + gcdc_webhook.CreateWebhookRequest(), + parent="parent_value", + webhook=gcdc_webhook.Webhook(name="name_value"), + ) + + +def test_create_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gcdc_webhook.UpdateWebhookRequest, + dict, + ], +) +def test_update_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + request_init["webhook"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook( + name="name_value", + display_name="display_name_value", + disabled=True, + generic_web_service=gcdc_webhook.Webhook.GenericWebService(uri="uri_value"), + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_webhook(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gcdc_webhook.Webhook) + assert response.name == "name_value" + assert response.display_name == "display_name_value" + assert response.disabled is True + + +def test_update_webhook_rest_required_fields( + request_type=gcdc_webhook.UpdateWebhookRequest, +): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_webhook._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("update_mask",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(("updateMask",)) & set(("webhook",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "post_update_webhook" + ) as post, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_update_webhook" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gcdc_webhook.UpdateWebhookRequest.pb( + gcdc_webhook.UpdateWebhookRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gcdc_webhook.Webhook.to_json(gcdc_webhook.Webhook()) + + request = gcdc_webhook.UpdateWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gcdc_webhook.Webhook() + + client.update_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_webhook_rest_bad_request( + transport: str = "rest", request_type=gcdc_webhook.UpdateWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + request_init["webhook"] = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4", + "display_name": "display_name_value", + "generic_web_service": { + "uri": "uri_value", + "username": "username_value", + "password": "password_value", + "request_headers": {}, + "allowed_ca_certs": [b"allowed_ca_certs_blob1", b"allowed_ca_certs_blob2"], + }, + "service_directory": {"service": "service_value", "generic_web_service": {}}, + "timeout": {"seconds": 751, "nanos": 543}, + "disabled": True, + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_webhook(request) + + +def test_update_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = gcdc_webhook.Webhook() + + # get arguments that satisfy an http rule for this method + sample_request = { + "webhook": { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + webhook=gcdc_webhook.Webhook(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gcdc_webhook.Webhook.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{webhook.name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_update_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_webhook( + gcdc_webhook.UpdateWebhookRequest(), + webhook=gcdc_webhook.Webhook(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + webhook.DeleteWebhookRequest, + dict, + ], +) +def test_delete_webhook_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_webhook(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_webhook_rest_required_fields(request_type=webhook.DeleteWebhookRequest): + transport_class = transports.WebhooksRestTransport + + request_init = {} + request_init["name"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_webhook._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_webhook._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("force",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_webhook(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_webhook_rest_unset_required_fields(): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_webhook._get_unset_required_fields({}) + assert set(unset_fields) == (set(("force",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_webhook_rest_interceptors(null_interceptor): + transport = transports.WebhooksRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.WebhooksRestInterceptor(), + ) + client = WebhooksClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.WebhooksRestInterceptor, "pre_delete_webhook" + ) as pre: + pre.assert_not_called() + pb_message = webhook.DeleteWebhookRequest.pb(webhook.DeleteWebhookRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = webhook.DeleteWebhookRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_webhook( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_webhook_rest_bad_request( + transport: str = "rest", request_type=webhook.DeleteWebhookRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_webhook(request) + + +def test_delete_webhook_rest_flattened(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = { + "name": "projects/sample1/locations/sample2/agents/sample3/webhooks/sample4" + } + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_webhook(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v3beta1/{name=projects/*/locations/*/agents/*/webhooks/*}" + % client.transport._host, + args[1], + ) + + +def test_delete_webhook_rest_flattened_error(transport: str = "rest"): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_webhook( + webhook.DeleteWebhookRequest(), + name="name_value", + ) + + +def test_delete_webhook_rest_error(): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = WebhooksClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = WebhooksClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = WebhooksClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = WebhooksClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.WebhooksGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.WebhooksGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.WebhooksGrpcTransport, + transports.WebhooksGrpcAsyncIOTransport, + transports.WebhooksRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = WebhooksClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.WebhooksGrpcTransport, + ) + + +def test_webhooks_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.WebhooksTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_webhooks_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.dialogflowcx_v3beta1.services.webhooks.transports.WebhooksTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.WebhooksTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "list_webhooks", + "get_webhook", + "create_webhook", + "update_webhook", + "delete_webhook", + "get_location", + "list_locations", + "get_operation", + "cancel_operation", + "list_operations", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_webhooks_base_transport_with_credentials_file(): # Instantiate the base transport with a credentials file with mock.patch.object( google.auth, "load_credentials_from_file", autospec=True @@ -2274,6 +3775,7 @@ def test_webhooks_transport_auth_adc(transport_class): [ transports.WebhooksGrpcTransport, transports.WebhooksGrpcAsyncIOTransport, + transports.WebhooksRestTransport, ], ) def test_webhooks_transport_auth_gdch_credentials(transport_class): @@ -2371,11 +3873,23 @@ def test_webhooks_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_webhooks_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.WebhooksRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + @pytest.mark.parametrize( "transport_name", [ "grpc", "grpc_asyncio", + "rest", ], ) def test_webhooks_host_no_port(transport_name): @@ -2386,7 +3900,11 @@ def test_webhooks_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:443") + assert client.transport._host == ( + "dialogflow.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com" + ) @pytest.mark.parametrize( @@ -2394,6 +3912,7 @@ def test_webhooks_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_webhooks_host_with_port(transport_name): @@ -2404,7 +3923,45 @@ def test_webhooks_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("dialogflow.googleapis.com:8000") + assert client.transport._host == ( + "dialogflow.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://dialogflow.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_webhooks_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = WebhooksClient( + credentials=creds1, + transport=transport_name, + ) + client2 = WebhooksClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.list_webhooks._session + session2 = client2.transport.list_webhooks._session + assert session1 != session2 + session1 = client1.transport.get_webhook._session + session2 = client2.transport.get_webhook._session + assert session1 != session2 + session1 = client1.transport.create_webhook._session + session2 = client2.transport.create_webhook._session + assert session1 != session2 + session1 = client1.transport.update_webhook._session + session2 = client2.transport.update_webhook._session + assert session1 != session2 + session1 = client1.transport.delete_webhook._session + session2 = client2.transport.delete_webhook._session + assert session1 != session2 def test_webhooks_grpc_transport_channel(): @@ -2723,6 +4280,292 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_location_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.GetLocationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/locations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_location(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.GetLocationRequest, + dict, + ], +) +def test_get_location_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/locations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.Location() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_location(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.Location) + + +def test_list_locations_rest_bad_request( + transport: str = "rest", request_type=locations_pb2.ListLocationsRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_locations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + locations_pb2.ListLocationsRequest, + dict, + ], +) +def test_list_locations_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = locations_pb2.ListLocationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_locations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, locations_pb2.ListLocationsResponse) + + +def test_cancel_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.CancelOperationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.cancel_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.CancelOperationRequest, + dict, + ], +) +def test_cancel_operation_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "{}" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.cancel_operation(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_get_operation_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.GetOperationRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"name": "projects/sample1/operations/sample2"}, request + ) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_operation(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.GetOperationRequest, + dict, + ], +) +def test_get_operation_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1/operations/sample2"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_operation(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.Operation) + + +def test_list_operations_rest_bad_request( + transport: str = "rest", request_type=operations_pb2.ListOperationsRequest +): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict({"name": "projects/sample1"}, request) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_operations(request) + + +@pytest.mark.parametrize( + "request_type", + [ + operations_pb2.ListOperationsRequest, + dict, + ], +) +def test_list_operations_rest(request_type): + client = WebhooksClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"name": "projects/sample1"} + request = request_type(**request_init) + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.ListOperationsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_operations(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, operations_pb2.ListOperationsResponse) + + def test_cancel_operation(transport: str = "grpc"): client = WebhooksClient( credentials=ga_credentials.AnonymousCredentials(), @@ -3440,6 +5283,7 @@ async def test_get_location_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -3457,6 +5301,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: From f00ed7c55c60c14f8b7bd5c7b213f04b432384f5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 14:28:23 -0800 Subject: [PATCH 7/7] chore(main): release 1.19.0 (#505) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ google/cloud/dialogflowcx/gapic_version.py | 2 +- google/cloud/dialogflowcx_v3/gapic_version.py | 2 +- .../cloud/dialogflowcx_v3beta1/gapic_version.py | 2 +- ...et_metadata_google.cloud.dialogflow.cx.v3.json | 2 +- ...tadata_google.cloud.dialogflow.cx.v3beta1.json | 2 +- 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 18e9f06a..d0d087a3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.18.0" + ".": "1.19.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 10fc63df..36b1deb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.19.0](https://github.com/googleapis/python-dialogflow-cx/compare/v1.18.0...v1.19.0) (2023-02-28) + + +### Features + +* Added gcs.proto. added support for GcsDestination and TextToSpeechSettings ([acfd1a1](https://github.com/googleapis/python-dialogflow-cx/commit/acfd1a11aca9c9cba7fb351f1781fa73c1e1d985)) +* Added persist_parameter_changes field from query_params to MatchIntentRequest ([acfd1a1](https://github.com/googleapis/python-dialogflow-cx/commit/acfd1a11aca9c9cba7fb351f1781fa73c1e1d985)) +* Enable "rest" transport in Python for services supporting numeric enums ([acfd1a1](https://github.com/googleapis/python-dialogflow-cx/commit/acfd1a11aca9c9cba7fb351f1781fa73c1e1d985)) +* Remove [REQUIRED] for VersionConfig ([acfd1a1](https://github.com/googleapis/python-dialogflow-cx/commit/acfd1a11aca9c9cba7fb351f1781fa73c1e1d985)) + + +### Documentation + +* Add more meaningful comments ([acfd1a1](https://github.com/googleapis/python-dialogflow-cx/commit/acfd1a11aca9c9cba7fb351f1781fa73c1e1d985)) + ## [1.18.0](https://github.com/googleapis/python-dialogflow-cx/compare/v1.17.1...v1.18.0) (2023-01-30) diff --git a/google/cloud/dialogflowcx/gapic_version.py b/google/cloud/dialogflowcx/gapic_version.py index 0028cb17..86f8ab82 100644 --- a/google/cloud/dialogflowcx/gapic_version.py +++ b/google/cloud/dialogflowcx/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.18.0" # {x-release-please-version} +__version__ = "1.19.0" # {x-release-please-version} diff --git a/google/cloud/dialogflowcx_v3/gapic_version.py b/google/cloud/dialogflowcx_v3/gapic_version.py index 0028cb17..86f8ab82 100644 --- a/google/cloud/dialogflowcx_v3/gapic_version.py +++ b/google/cloud/dialogflowcx_v3/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.18.0" # {x-release-please-version} +__version__ = "1.19.0" # {x-release-please-version} diff --git a/google/cloud/dialogflowcx_v3beta1/gapic_version.py b/google/cloud/dialogflowcx_v3beta1/gapic_version.py index 0028cb17..86f8ab82 100644 --- a/google/cloud/dialogflowcx_v3beta1/gapic_version.py +++ b/google/cloud/dialogflowcx_v3beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.18.0" # {x-release-please-version} +__version__ = "1.19.0" # {x-release-please-version} diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json index 4fe8e9ba..7b8b2aa7 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-dialogflow-cx", - "version": "0.1.0" + "version": "1.19.0" }, "snippets": [ { diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json index bddeff21..568546a7 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.cx.v3beta1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-dialogflow-cx", - "version": "0.1.0" + "version": "1.19.0" }, "snippets": [ {