diff --git a/samples/interactive-tutorials/README.md b/samples/interactive-tutorials/README.md new file mode 100644 index 00000000..17593f8a --- /dev/null +++ b/samples/interactive-tutorials/README.md @@ -0,0 +1,151 @@ +# Google Retail API: Python Code Samples + +## Get started with the Google Cloud Retail API + +The Retail API provides you with the following possibilities to: + - Create and maintaining the catalog data. + - Fine-tune the search configuration. + - Import and maintain the user events data. + +You can find the information about the Retail services in the [documentation](https://cloud.google.com/retail/docs) + + +If you would like to have a closer look at the Retail API features and try them yourself, +the best option is to use the [Interactive Tutorials](https://cloud.google.com/retail/docs/overview). The tutorials will be launched in the CloudShell environment, and you will be able to request the Retail services and check the response with minimum time and effort. + +The code samples in the directory **python-retail/samples/interactive-tutorials** are explicitly created for use with the Retail Interactive Tutorials. + +If, for some reason, you have decided to proceed with these code samples without the tutorial, please go through the following steps and set up the required preconditions. + +### Select your project and enable the Retail API + +Google Cloud organizes resources into projects. This lets you +collect all the related resources for a single application in one place. + +If you don't have a Google Cloud project yet or you're not the owner of an existing one, you can +[create a new project](https://console.cloud.google.com/projectcreate). + +After the project is created, set your PROJECT_ID to a ```project``` variable. +1. Run the following command in Terminal: + ```bash + gcloud config set project + ``` + +1. To check that the Retail API is enabled for your Project, go to the [Admin Console](https://console.cloud.google.com/ai/retail/). + +### Create service account + +To access the Retail API, you must create a service account. + +1. To create a service account, follow this [instruction](https://cloud.google.com/retail/docs/setting-up#service-account) + +1. Find your service account on the [IAM page](https://console.cloud.google.com/iam-admin/iam), + click `Edit` icon, add the 'Storage Admin' and 'BigQuery Admin' roles. It may take some time for changes to apply. + +1. Copy the service account email in the Principal field. + +### Set up authentication + +To run a code sample from the Cloud Shell, you need to be authenticated using the service account credentials. + +1. Login with your user credentials. + ```bash + gcloud auth login + ``` + +1. Type `Y` and press **Enter**. Click the link in a Terminal. A browser window should appear asking you to log in using your Gmail account. + +1. Provide the Google Auth Library with access to your credentials and paste the code from the browser to the Terminal. + +1. Upload your service account key JSON file and use it to activate the service account: + + ```bash + gcloud iam service-accounts keys create ~/key.json --iam-account + ``` + + ```bash + gcloud auth activate-service-account --key-file ~/key.json + ``` + +1. To request the Retail API, set your service account key JSON file as the GOOGLE_APPLICATION_CREDENTIALS environment variable : + ```bash + export GOOGLE_APPLICATION_CREDENTIALS=~/key.json + ``` + +### Set the PROJECT_NUMBER and PROJECT_ID environment variables + +You will run the code samples in your own Google Cloud project. To use the **project_number** and **project_id** in every request to the Retail API, you should first specify them as environment variables. + +1. Find the project number and project ID in the Project Info card displayed on **Home/Dashboard**. + +1. Set the **project_number** with the following command: + ```bash + export PROJECT_NUMBER= + ``` +1. Set the **project_id** with the following command: + ```bash + export PROJECT_ID= + ``` + +### Install Google Cloud Retail libraries + +To run Python code samples for the Retail API tutorial, you need to set up your virtual environment. + +1. Run the following commands in a Terminal to create an isolated Python environment: + ```bash + pip install virtualenv + virtualenv myenv + source myenv/bin/activate + ``` +1. Next, install Google packages: + ```bash + pip install google + pip install google-cloud-retail + pip install google-cloud-storage + pip install google-cloud-bigquery + + ``` + +## Import Catalog Data + +This step is required if this is the first Retail API Tutorial you run. +Otherwise, you can skip it. + +### Upload catalog data to Cloud Storage + +There is a JSON file with valid products prepared in the `product` directory: +`product/resources/products.json`. + +Another file, `product/resources/products_some_invalid.json`, contains both valid and invalid products, and you will use it to check the error handling. + +In your own project, create a Cloud Storage bucket and put the JSON file there. +The bucket name must be unique. For convenience, you can name it `_`. + +1. To create the bucket and upload the JSON file, run the following command in the Terminal: + + ```bash + python product/setup/create_gcs_bucket.py + ``` + + Now you can see the bucket is created in the [Cloud Storage](https://console.cloud.google.com/storage/browser), and the files are uploaded. + +1. The name of the created Retail Search bucket is printed in the Terminal. Copy the name and set it as the environment variable `BUCKET_NAME`: + + ```bash + export BUCKET_NAME= + ``` + +### Import products to the Retail Catalog + +To import the prepared products to a catalog, run the following command in the Terminal: + +```bash +python product/import_products_gcs.py +``` + +## Run your code sample + +Run the sample in a terminal with the following command: +```bash +python search/search_with_boost_spec.py +``` \ No newline at end of file diff --git a/samples/interactive-tutorials/noxfile.py b/samples/interactive-tutorials/noxfile.py new file mode 100644 index 00000000..1e30748a --- /dev/null +++ b/samples/interactive-tutorials/noxfile.py @@ -0,0 +1,269 @@ +# Copyright 2019 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 print_function + +import os +from pathlib import Path +import sys +from typing import Callable, Dict, List, Optional + +import nox + + +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING +# DO NOT EDIT THIS FILE EVER! +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING + +BLACK_VERSION = "black==19.10b0" + +# Copy `noxfile_config.py` to your directory and modify it instead. + +# `TEST_CONFIG` dict is a configuration hook that allows users to +# modify the test configurations. The values here should be in sync +# with `noxfile_config.py`. Users will copy `noxfile_config.py` into +# their directory and modify it. + +TEST_CONFIG = { + # You can opt out from the test for specific Python versions. + "ignored_versions": [], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": False, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} + + +try: + # Ensure we can import noxfile_config in the project's directory. + sys.path.append(".") + from noxfile_config import TEST_CONFIG_OVERRIDE +except ImportError as e: + print("No user noxfile_config found: detail: {}".format(e)) + TEST_CONFIG_OVERRIDE = {} + +# Update the TEST_CONFIG with the user supplied values. +TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) + + +def get_pytest_env_vars() -> Dict[str, str]: + """Returns a dict for pytest invocation.""" + ret = {} + + # Override the GCLOUD_PROJECT and the alias. + env_key = TEST_CONFIG["gcloud_project_env"] + # This should error out if not set. + ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] + + # Apply user supplied envs. + ret.update(TEST_CONFIG["envs"]) + return ret + + +# DO NOT EDIT - automatically generated. +# All versions used to test samples. +ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] + +# Any default versions that should be ignored. +IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] + +TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) + +INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( + "True", + "true", +) + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + +# +# Style Checks +# + + +def _determine_local_import_names(start_dir: str) -> List[str]: + """Determines all import names that should be considered "local". + This is used when running the linter to insure that import order is + properly checked. + """ + file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] + return [ + basename + for basename, extension in file_ext_pairs + if extension == ".py" + or os.path.isdir(os.path.join(start_dir, basename)) + and basename not in ("__pycache__") + ] + + +# Linting with flake8. +# +# We ignore the following rules: +# E203: whitespace before ‘:’ +# E266: too many leading ‘#’ for block comment +# E501: line too long +# I202: Additional newline in a section of imports +# +# We also need to specify the rules which are ignored by default: +# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] +FLAKE8_COMMON_ARGS = [ + "--show-source", + "--builtin=gettext", + "--max-complexity=20", + "--import-order-style=google", + "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", + "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", + "--max-line-length=88", +] + + +@nox.session +def lint(session: nox.sessions.Session) -> None: + if not TEST_CONFIG["enforce_type_hints"]: + session.install("flake8", "flake8-import-order") + else: + session.install("flake8", "flake8-import-order", "flake8-annotations") + + local_names = _determine_local_import_names(".") + args = FLAKE8_COMMON_ARGS + [ + "--application-import-names", + ",".join(local_names), + ".", + ] + session.run("flake8", *args) + + +# +# Black +# + + +@nox.session +def blacken(session: nox.sessions.Session) -> None: + session.install(BLACK_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + session.run("black", *python_files) + + +# +# Sample Tests +# + + +PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] + + +def _session_tests( + session: nox.sessions.Session, post_install: Callable = None +) -> None: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) + + +@nox.session(python=ALL_VERSIONS) +def py(session: nox.sessions.Session) -> None: + """Runs py.test for a sample using the specified version of Python.""" + if session.python in TESTED_VERSIONS: + _session_tests(session) + else: + session.skip( + "SKIPPED: {} tests are disabled for this sample.".format(session.python) + ) + + +# +# Readmegen +# + + +def _get_repo_root() -> Optional[str]: + """ Returns the root folder of the project. """ + # Get root of this repository. Assume we don't have directories nested deeper than 10 items. + p = Path(os.getcwd()) + for i in range(10): + if p is None: + break + if Path(p / ".git").exists(): + return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) + p = p.parent + raise Exception("Unable to detect repository root.") + + +GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) + + +@nox.session +@nox.parametrize("path", GENERATED_READMES) +def readmegen(session: nox.sessions.Session, path: str) -> None: + """(Re-)generates the readme for a sample.""" + session.install("jinja2", "pyyaml") + dir_ = os.path.dirname(path) + + if os.path.exists(os.path.join(dir_, "requirements.txt")): + session.install("-r", os.path.join(dir_, "requirements.txt")) + + in_file = os.path.join(dir_, "README.rst.in") + session.run( + "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file + ) diff --git a/samples/interactive-tutorials/noxfile_config.py b/samples/interactive-tutorials/noxfile_config.py new file mode 100644 index 00000000..9b9b1756 --- /dev/null +++ b/samples/interactive-tutorials/noxfile_config.py @@ -0,0 +1,35 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.6", "3.8", "3.9"], + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": { + "DATA_LABELING_API_ENDPOINT": "us-central1-autopush-aiplatform.sandbox.googleapis.com", + "PYTEST_ADDOPTS": "-n=auto", # Run tests parallel using all available CPUs + }, +} diff --git a/samples/interactive-tutorials/search/noxfile.py b/samples/interactive-tutorials/search/noxfile.py new file mode 100644 index 00000000..20cdfc62 --- /dev/null +++ b/samples/interactive-tutorials/search/noxfile.py @@ -0,0 +1,279 @@ +# Copyright 2019 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 print_function + +import glob +import os +from pathlib import Path +import sys +from typing import Callable, Dict, List, Optional + +import nox + + +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING +# DO NOT EDIT THIS FILE EVER! +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING + +BLACK_VERSION = "black==19.10b0" + +# Copy `noxfile_config.py` to your directory and modify it instead. + +# `TEST_CONFIG` dict is a configuration hook that allows users to +# modify the test configurations. The values here should be in sync +# with `noxfile_config.py`. Users will copy `noxfile_config.py` into +# their directory and modify it. + +TEST_CONFIG = { + # You can opt out from the test for specific Python versions. + "ignored_versions": [], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": False, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} + + +try: + # Ensure we can import noxfile_config in the project's directory. + sys.path.append(".") + from noxfile_config import TEST_CONFIG_OVERRIDE +except ImportError as e: + print("No user noxfile_config found: detail: {}".format(e)) + TEST_CONFIG_OVERRIDE = {} + +# Update the TEST_CONFIG with the user supplied values. +TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) + + +def get_pytest_env_vars() -> Dict[str, str]: + """Returns a dict for pytest invocation.""" + ret = {} + + # Override the GCLOUD_PROJECT and the alias. + env_key = TEST_CONFIG["gcloud_project_env"] + # This should error out if not set. + ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] + + # Apply user supplied envs. + ret.update(TEST_CONFIG["envs"]) + return ret + + +# DO NOT EDIT - automatically generated. +# All versions used to test samples. +ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] + +# Any default versions that should be ignored. +IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] + +TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) + +INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( + "True", + "true", +) + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + +# +# Style Checks +# + + +def _determine_local_import_names(start_dir: str) -> List[str]: + """Determines all import names that should be considered "local". + + This is used when running the linter to insure that import order is + properly checked. + """ + file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] + return [ + basename + for basename, extension in file_ext_pairs + if extension == ".py" + or os.path.isdir(os.path.join(start_dir, basename)) + and basename not in ("__pycache__") + ] + + +# Linting with flake8. +# +# We ignore the following rules: +# E203: whitespace before ‘:’ +# E266: too many leading ‘#’ for block comment +# E501: line too long +# I202: Additional newline in a section of imports +# +# We also need to specify the rules which are ignored by default: +# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] +FLAKE8_COMMON_ARGS = [ + "--show-source", + "--builtin=gettext", + "--max-complexity=20", + "--import-order-style=google", + "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", + "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", + "--max-line-length=88", +] + + +@nox.session +def lint(session: nox.sessions.Session) -> None: + if not TEST_CONFIG["enforce_type_hints"]: + session.install("flake8", "flake8-import-order") + else: + session.install("flake8", "flake8-import-order", "flake8-annotations") + + local_names = _determine_local_import_names(".") + args = FLAKE8_COMMON_ARGS + [ + "--application-import-names", + ",".join(local_names), + ".", + ] + session.run("flake8", *args) + + +# +# Black +# + + +@nox.session +def blacken(session: nox.sessions.Session) -> None: + session.install(BLACK_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + session.run("black", *python_files) + + +# +# Sample Tests +# + + +PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] + + +def _session_tests( + session: nox.sessions.Session, post_install: Callable = None +) -> None: + # check for presence of tests + test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + test_list.extend(glob.glob("tests")) + if len(test_list) == 0: + print("No tests found, skipping directory.") + else: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) + + +@nox.session(python=ALL_VERSIONS) +def py(session: nox.sessions.Session) -> None: + """Runs py.test for a sample using the specified version of Python.""" + if session.python in TESTED_VERSIONS: + _session_tests(session) + else: + session.skip( + "SKIPPED: {} tests are disabled for this sample.".format(session.python) + ) + + +# +# Readmegen +# + + +def _get_repo_root() -> Optional[str]: + """ Returns the root folder of the project. """ + # Get root of this repository. Assume we don't have directories nested deeper than 10 items. + p = Path(os.getcwd()) + for i in range(10): + if p is None: + break + if Path(p / ".git").exists(): + return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) + p = p.parent + raise Exception("Unable to detect repository root.") + + +GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) + + +@nox.session +@nox.parametrize("path", GENERATED_READMES) +def readmegen(session: nox.sessions.Session, path: str) -> None: + """(Re-)generates the readme for a sample.""" + session.install("jinja2", "pyyaml") + dir_ = os.path.dirname(path) + + if os.path.exists(os.path.join(dir_, "requirements.txt")): + session.install("-r", os.path.join(dir_, "requirements.txt")) + + in_file = os.path.join(dir_, "README.rst.in") + session.run( + "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file + ) diff --git a/samples/interactive-tutorials/search/noxfile_config.py b/samples/interactive-tutorials/search/noxfile_config.py new file mode 100644 index 00000000..8eb8d16f --- /dev/null +++ b/samples/interactive-tutorials/search/noxfile_config.py @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.6"], + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": { + "BUCKET_NAME": "retail-interactive-tutorials", + }, +} diff --git a/samples/interactive-tutorials/search/requirements-test.txt b/samples/interactive-tutorials/search/requirements-test.txt new file mode 100644 index 00000000..bbf73145 --- /dev/null +++ b/samples/interactive-tutorials/search/requirements-test.txt @@ -0,0 +1,2 @@ +pytest==6.2.5 +pytest-xdist==2.5.0 diff --git a/samples/interactive-tutorials/search/requirements.txt b/samples/interactive-tutorials/search/requirements.txt new file mode 100644 index 00000000..0ba6ea71 --- /dev/null +++ b/samples/interactive-tutorials/search/requirements.txt @@ -0,0 +1,4 @@ +google==3.0.0 +google-cloud-retail==1.1.0 +google-cloud-storage==1.43.0 +google-cloud-bigquery==2.30.1 \ No newline at end of file diff --git a/samples/interactive-tutorials/search/search_simple_query.py b/samples/interactive-tutorials/search/search_simple_query.py new file mode 100644 index 00000000..78889209 --- /dev/null +++ b/samples/interactive-tutorials/search/search_simple_query.py @@ -0,0 +1,59 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_for_products_with_query_parameter] +# Call Retail API to search for a products in a catalog using only search query. +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + search_request = SearchRequest() + search_request.placement = default_search_placement # Placement is used to identify the Serving Config name. + search_request.query = query + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.page_size = 10 + + print("---search request:---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT QUERY PHRASES HERE: + query_phrase = "Hoodie" + + search_request = get_search_request(query_phrase) + search_response = SearchServiceClient().search(search_request) + + print("---search response---") + print(search_response) + return search_response + + +search() +# [END retail_search_for_products_with_query_parameter] diff --git a/samples/interactive-tutorials/search/search_simple_query_test.py b/samples/interactive-tutorials/search/search_simple_query_test.py new file mode 100644 index 00000000..32749e2a --- /dev/null +++ b/samples/interactive-tutorials/search/search_simple_query_test.py @@ -0,0 +1,39 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_simple_query import search + + +def test_search_simple_query_pass(): + output = str( + subprocess.check_output("python search_simple_query.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_simple_query_response(): + response = search() + + assert len(response.results) == 10 + product_title = response.results[0].product.title + assert re.match(".*Hoodie.*", product_title) diff --git a/samples/interactive-tutorials/search/search_with_boost_spec.py b/samples/interactive-tutorials/search/search_with_boost_spec.py new file mode 100644 index 00000000..7b2995d8 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_boost_spec.py @@ -0,0 +1,68 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_product_with_boost_spec] +# Call Retail API to search for a products in a catalog, rerank the +# results boosting or burying the products that match defined condition. +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str, condition: str, boost_strength: float): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + condition_boost_spec = SearchRequest.BoostSpec.ConditionBoostSpec() + condition_boost_spec.condition = condition + condition_boost_spec.boost = boost_strength + + boost_spec = SearchRequest.BoostSpec() + boost_spec.condition_boost_specs = [condition_boost_spec] + + search_request = SearchRequest() + search_request.placement = default_search_placement # Placement is used to identify the Serving Config name. + search_request.query = query + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.boost_spec = boost_spec + search_request.page_size = 10 + + print("---search request---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT CONDITIONS HERE: + condition = '(colorFamilies: ANY("Blue"))' + boost = 0.0 + + search_request = get_search_request("Tee", condition, boost) + search_response = SearchServiceClient().search(search_request) + print("---search response---") + print(search_response) + return search_response + + +search() +# [END retail_search_product_with_boost_spec] diff --git a/samples/interactive-tutorials/search/search_with_boost_spec_test.py b/samples/interactive-tutorials/search/search_with_boost_spec_test.py new file mode 100644 index 00000000..cf391b2e --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_boost_spec_test.py @@ -0,0 +1,39 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_boost_spec import search + + +def test_search_with_boost_spec_pass(): + output = str( + subprocess.check_output("python search_with_boost_spec.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_with_boost_spec(): + response = search() + + assert len(response.results) == 10 + product_title = response.results[0].product.title + assert re.match(".*Tee.*", product_title) diff --git a/samples/interactive-tutorials/search/search_with_facet_spec.py b/samples/interactive-tutorials/search/search_with_facet_spec.py new file mode 100644 index 00000000..50a8fb7f --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_facet_spec.py @@ -0,0 +1,66 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_product_with_facet_spec] +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str, facet_key_param: str): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + # PUT THE INTERVALS HERE: + facet_key = SearchRequest.FacetSpec().FacetKey() + facet_key.key = facet_key_param + + facet_spec = SearchRequest.FacetSpec() + facet_spec.facet_key = facet_key + + search_request = SearchRequest() + search_request.placement = default_search_placement # Placement is used to identify the Serving Config name. + search_request.query = query + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.facet_specs = [facet_spec] + search_request.page_size = 10 + + print("---search request---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT FACETS HERE: + facet_key = "colorFamilies" + + search_request = get_search_request("Tee", facet_key) + search_response = SearchServiceClient().search(search_request) + print("---search response---") + print(search_response) + print("---facets:---") + print(search_response.facets) + return search_response + + +search() +# [END retail_search_product_with_facet_spec] diff --git a/samples/interactive-tutorials/search/search_with_facet_spec_test.py b/samples/interactive-tutorials/search/search_with_facet_spec_test.py new file mode 100644 index 00000000..df082ac2 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_facet_spec_test.py @@ -0,0 +1,41 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_facet_spec import search + + +def test_search_with_filtering_pass(): + output = str( + subprocess.check_output("python search_with_facet_spec.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + assert re.match(".*facets.*?colorFamilies.*", output) + + +def test_search_with_filtering(): + response = search() + + assert len(response.results) == 10 + product_title = response.results[0].product.title + assert re.match(".*Tee.*", product_title) + assert response.facets[0].key == "colorFamilies" diff --git a/samples/interactive-tutorials/search/search_with_filtering.py b/samples/interactive-tutorials/search/search_with_filtering.py new file mode 100644 index 00000000..e7a8a31d --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_filtering.py @@ -0,0 +1,60 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_for_products_with_filter] +# Call Retail API to search for a products in a catalog, filter the results by different product fields. +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str, _filter: str): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + search_request = SearchRequest() + search_request.placement = default_search_placement # Placement is used to identify the Serving Config name. + search_request.query = query + search_request.filter = _filter + search_request.page_size = 10 + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.page_size = 10 + + print("---search request:---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT FILTER EXPRESSIONS HERE: + filter_ = '(colorFamilies: ANY("Black"))' + + search_request = get_search_request("Tee", filter_) + search_response = SearchServiceClient().search(search_request) + print("---search response---") + print(search_response) + return search_response + + +search() +# [END retail_search_for_products_with_filter] diff --git a/samples/interactive-tutorials/search/search_with_filtering_test.py b/samples/interactive-tutorials/search/search_with_filtering_test.py new file mode 100644 index 00000000..270ff193 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_filtering_test.py @@ -0,0 +1,42 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_filtering import search + + +def test_search_with_filtering_pass(): + output = str( + subprocess.check_output("python search_with_filtering.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_with_filtering(): + response = search() + + assert len(response.results) == 10 + product_title = response.results[0].product.title + assert re.match(".*Tee.*", product_title) + assert re.match(".*Black.*", product_title) + assert "Black" in response.results[0].product.color_info.color_families + assert response.total_size == 16 diff --git a/samples/interactive-tutorials/search/search_with_ordering.py b/samples/interactive-tutorials/search/search_with_ordering.py new file mode 100644 index 00000000..118d4202 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_ordering.py @@ -0,0 +1,60 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_for_products_with_ordering] +# Call Retail API to search for a products in a catalog, order the results by different product fields. +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str, order: str): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + search_request = SearchRequest() + search_request.placement = default_search_placement + search_request.query = query + search_request.order_by = order + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.page_size = 10 + + print("---search request---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT ORDERING EXPRESSIONS HERE: + order = "price desc" + + search_request = get_search_request("Hoodie", order) + search_response = SearchServiceClient().search(search_request) + + print("---search response---") + print(search_response) + return search_response + + +search() +# [END retail_search_for_products_with_ordering] diff --git a/samples/interactive-tutorials/search/search_with_ordering_test.py b/samples/interactive-tutorials/search/search_with_ordering_test.py new file mode 100644 index 00000000..0de2c979 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_ordering_test.py @@ -0,0 +1,38 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_ordering import search + + +def test_search_with_ordering_pass(): + output = str( + subprocess.check_output("python search_with_ordering.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_with_ordering(): + response = search() + + assert len(response.results) == 10 + assert response.results[0].product.price_info.price == 39 diff --git a/samples/interactive-tutorials/search/search_with_pagination.py b/samples/interactive-tutorials/search/search_with_pagination.py new file mode 100644 index 00000000..727227c2 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_pagination.py @@ -0,0 +1,72 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_for_products_with_page_size] +# Call Retail API to search for a products in a catalog, +# limit the number of the products per page and go to the next page using "next_page_token" +# or jump to chosen page using "offset". +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request(query: str, page_size: int, offset: int, next_page_token: str): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + search_request = SearchRequest() + search_request.placement = default_search_placement + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.query = query + search_request.page_size = page_size + search_request.offset = offset + search_request.page_token = next_page_token + + print("---search request:---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT PAGINATION PARAMETERS HERE: + page_size = 6 + offset = 0 + page_token = "" + + search_request_first_page = get_search_request( + "Hoodie", page_size, offset, page_token + ) + search_response_first_page = SearchServiceClient().search(search_request_first_page) + + print("---search response---") + print(search_response_first_page) + + # PASTE CALL WITH NEXT PAGE TOKEN HERE: + + # PASTE CALL WITH OFFSET HERE: + + return search_response_first_page + + +search() +# [END retail_search_for_products_with_page_size] diff --git a/samples/interactive-tutorials/search/search_with_pagination_test.py b/samples/interactive-tutorials/search/search_with_pagination_test.py new file mode 100644 index 00000000..49bcd63a --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_pagination_test.py @@ -0,0 +1,39 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_pagination import search + + +def test_search_with_pagination_pass(): + output = str( + subprocess.check_output("python search_with_pagination.py", shell=True) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_with_pagination(): + response = search() + + product_title = response.results[0].product.title + assert re.match(".*Hoodie", product_title) + assert len(response.results) == 6 diff --git a/samples/interactive-tutorials/search/search_with_query_expansion_spec.py b/samples/interactive-tutorials/search/search_with_query_expansion_spec.py new file mode 100644 index 00000000..9455e900 --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_query_expansion_spec.py @@ -0,0 +1,66 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# [START retail_search_for_products_with_query_expansion_specification] +# Call Retail API to search for a products in a catalog, +# enabling the query expansion feature to let the Google Retail Search build an automatic query expansion. +# +import os + +from google.cloud.retail import SearchRequest, SearchServiceClient + +project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] + + +# get search service request: +def get_search_request( + query: str, condition: SearchRequest.QueryExpansionSpec.Condition +): + default_search_placement = ( + "projects/" + + project_number + + "/locations/global/catalogs/default_catalog/placements/default_search" + ) + + query_expansion_spec = SearchRequest().QueryExpansionSpec() + query_expansion_spec.condition = condition + + search_request = SearchRequest() + search_request.placement = default_search_placement # Placement is used to identify the Serving Config name. + search_request.query = query + search_request.visitor_id = "123456" # A unique identifier to track visitors + search_request.query_expansion_spec = query_expansion_spec + search_request.page_size = 10 + + print("---search request:---") + print(search_request) + + return search_request + + +# call the Retail Search: +def search(): + # TRY DIFFERENT QUERY EXPANSION CONDITION HERE: + condition = SearchRequest.QueryExpansionSpec.Condition.AUTO + + search_request = get_search_request("Google Youth Hero Tee Grey", condition) + search_response = SearchServiceClient().search(search_request) + + print("---search response---") + print(search_response) + return search_response + + +search() +# [END retail_search_for_products_with_query_expansion_specification] diff --git a/samples/interactive-tutorials/search/search_with_query_expansion_spec_test.py b/samples/interactive-tutorials/search/search_with_query_expansion_spec_test.py new file mode 100644 index 00000000..b9c04e3f --- /dev/null +++ b/samples/interactive-tutorials/search/search_with_query_expansion_spec_test.py @@ -0,0 +1,41 @@ +# Copyright 2021 Google Inc. All Rights Reserved. +# +# 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. + +# Call Retail API to search for a products in a catalog using only search query. +# +import re +import subprocess + +from search_with_query_expansion_spec import search + + +def test_search_with_query_expansion_spec_pass(): + output = str( + subprocess.check_output( + "python search_with_query_expansion_spec.py", shell=True + ) + ) + + assert re.match(".*search request.*", output) + assert re.match(".*search response.*", output) + # check the response contains some products + assert re.match(".*results.*id.*", output) + + +def test_search_with_query_expansion_spec(): + response = search() + + assert response.results[0].product.title == "Google Youth Hero Tee Grey" + assert response.results[2].product.title != "Google Youth Hero Tee Grey" + assert response.query_expansion_info.expanded_query is True diff --git a/samples/snippets/create_test_resources.py b/samples/snippets/create_test_resources.py index 22005b40..e56dd575 100644 --- a/samples/snippets/create_test_resources.py +++ b/samples/snippets/create_test_resources.py @@ -23,7 +23,7 @@ ImportProductsRequest, ProductInputConfig from google.cloud.retail_v2 import ProductServiceClient -project_number = os.getenv('PROJECT_NUMBER') +project_number = os.getenv('GOOGLE_CLOUD_PROJECT_NUMBER') bucket_name = os.getenv('BUCKET_NAME') storage_client = storage.Client() resource_file = "resources/products.json" diff --git a/samples/snippets/remove_test_resources.py b/samples/snippets/remove_test_resources.py index df1c6ded..26891705 100644 --- a/samples/snippets/remove_test_resources.py +++ b/samples/snippets/remove_test_resources.py @@ -21,7 +21,7 @@ from google.cloud.retail import DeleteProductRequest, ListProductsRequest, \ ProductServiceClient -project_number = os.getenv('PROJECT_NUMBER') +project_number = os.getenv('GOOGLE_CLOUD_PROJECT_NUMBER') bucket_name = os.getenv('BUCKET_NAME') default_catalog = "projects/{0}/locations/global/catalogs/default_catalog/branches/default_branch".format(