From 02a48d710db7bb5fe3b46d51d421d1b24375e103 Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Mon, 22 Jan 2024 10:44:10 +0530 Subject: [PATCH 1/9] docs: samples and tests for auto-generated createDatabase and createInstance APIs. --- samples/generated/conftest.py | 293 ++++++++++++++++++++++++ samples/generated/noxfile.py | 293 ++++++++++++++++++++++++ samples/generated/requirements-test.txt | 4 + samples/generated/requirements.txt | 2 + samples/generated/snippets.py | 104 +++++++++ samples/generated/snippets_test.py | 207 +++++++++++++++++ 6 files changed, 903 insertions(+) create mode 100644 samples/generated/conftest.py create mode 100644 samples/generated/noxfile.py create mode 100644 samples/generated/requirements-test.txt create mode 100644 samples/generated/requirements.txt create mode 100644 samples/generated/snippets.py create mode 100644 samples/generated/snippets_test.py diff --git a/samples/generated/conftest.py b/samples/generated/conftest.py new file mode 100644 index 0000000000..75e4082a20 --- /dev/null +++ b/samples/generated/conftest.py @@ -0,0 +1,293 @@ +# Copyright 2024 Google LLC 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. +""" Shared pytest fixtures.""" + +import time +import uuid + +from google.api_core import exceptions + +from google.cloud import spanner_admin_database_v1 +from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect +from google.cloud.spanner_v1 import backup +from google.cloud.spanner_v1 import client +from google.cloud.spanner_v1 import database +from google.cloud.spanner_v1 import instance +import pytest +from test_utils import retry + +INSTANCE_CREATION_TIMEOUT = 560 # seconds + +OPERATION_TIMEOUT_SECONDS = 120 # seconds + +retry_429 = retry.RetryErrors(exceptions.ResourceExhausted, delay=15) + + +@pytest.fixture(scope="module") +def sample_name(): + """Sample testcase modules must define this fixture. + + The name is used to label the instance created by the sample, to + aid in debugging leaked instances. + """ + raise NotImplementedError("Define 'sample_name' fixture in sample test driver") + + +@pytest.fixture(scope="module") +def database_dialect(): + """Database dialect to be used for this sample. + + The dialect is used to initialize the dialect for the database. + It can either be GoogleStandardSql or PostgreSql. + """ + # By default, we consider GOOGLE_STANDARD_SQL dialect. Other specific tests + # can override this if required. + return DatabaseDialect.GOOGLE_STANDARD_SQL + + +@pytest.fixture(scope="session") +def spanner_client(): + """Shared client used across all samples in a session.""" + return client.Client() + + +def scrub_instance_ignore_not_found(to_scrub): + """Helper for func:`cleanup_old_instances`""" + try: + for backup_pb in to_scrub.list_backups(): + backup.Backup.from_pb(backup_pb, to_scrub).delete() + + retry_429(to_scrub.delete)() + except exceptions.NotFound: + pass + + +@pytest.fixture(scope="session") +def cleanup_old_instances(spanner_client): + """Delete instances, created by samples, that are older than an hour.""" + cutoff = int(time.time()) - 1 * 60 * 60 + instance_filter = "labels.cloud_spanner_samples:true" + + for instance_pb in spanner_client.list_instances(filter_=instance_filter): + inst = instance.Instance.from_pb(instance_pb, spanner_client) + + if "created" in inst.labels: + create_time = int(inst.labels["created"]) + + if create_time <= cutoff: + scrub_instance_ignore_not_found(inst) + + +@pytest.fixture(scope="module") +def instance_id(): + """Unique id for the instance used in samples.""" + return f"test-instance-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def multi_region_instance_id(): + """Unique id for the multi-region instance used in samples.""" + return f"multi-instance-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def instance_config(spanner_client): + return "{}/instanceConfigs/{}".format( + spanner_client.project_name, "regional-us-central1" + ) + + +@pytest.fixture(scope="module") +def multi_region_instance_config(spanner_client): + return "{}/instanceConfigs/{}".format(spanner_client.project_name, "nam3") + + +@pytest.fixture(scope="module") +def sample_instance( + spanner_client, + cleanup_old_instances, + instance_id, + instance_config, + sample_name, +): + sample_instance = spanner_client.instance( + instance_id, + instance_config, + labels={ + "cloud_spanner_samples": "true", + "sample_name": sample_name, + "created": str(int(time.time())), + }, + ) + op = retry_429(sample_instance.create)() + op.result(INSTANCE_CREATION_TIMEOUT) # block until completion + + # Eventual consistency check + retry_found = retry.RetryResult(bool) + retry_found(sample_instance.exists)() + + yield sample_instance + + for database_pb in sample_instance.list_databases(): + database.Database.from_pb(database_pb, sample_instance).drop() + + for backup_pb in sample_instance.list_backups(): + backup.Backup.from_pb(backup_pb, sample_instance).delete() + + sample_instance.delete() + + +@pytest.fixture(scope="module") +def multi_region_instance( + spanner_client, + cleanup_old_instances, + multi_region_instance_id, + multi_region_instance_config, + sample_name, +): + multi_region_instance = spanner_client.instance( + multi_region_instance_id, + multi_region_instance_config, + labels={ + "cloud_spanner_samples": "true", + "sample_name": sample_name, + "created": str(int(time.time())), + }, + ) + op = retry_429(multi_region_instance.create)() + op.result(INSTANCE_CREATION_TIMEOUT) # block until completion + + # Eventual consistency check + retry_found = retry.RetryResult(bool) + retry_found(multi_region_instance.exists)() + + yield multi_region_instance + + for database_pb in multi_region_instance.list_databases(): + database.Database.from_pb(database_pb, multi_region_instance).drop() + + for backup_pb in multi_region_instance.list_backups(): + backup.Backup.from_pb(backup_pb, multi_region_instance).delete() + + multi_region_instance.delete() + + +@pytest.fixture(scope="module") +def database_id(): + """Id for the database used in samples. + + Sample testcase modules can override as needed. + """ + return "my-database-id" + + +@pytest.fixture(scope="module") +def bit_reverse_sequence_database_id(): + """Id for the database used in bit reverse sequence samples. + + Sample testcase modules can override as needed. + """ + return "sequence-database-id" + + +@pytest.fixture(scope="module") +def database_ddl(): + """Sequence of DDL statements used to set up the database. + + Sample testcase modules can override as needed. + """ + return [] + + +@pytest.fixture(scope="module") +def sample_database( + spanner_client, sample_instance, database_id, database_ddl, database_dialect +): + if database_dialect == DatabaseDialect.POSTGRESQL: + sample_database = sample_instance.database( + database_id, + database_dialect=DatabaseDialect.POSTGRESQL, + ) + + if not sample_database.exists(): + operation = sample_database.create() + operation.result(OPERATION_TIMEOUT_SECONDS) + + request = spanner_admin_database_v1.UpdateDatabaseDdlRequest( + database=sample_database.name, + statements=database_ddl, + ) + + operation = spanner_client.database_admin_api.update_database_ddl(request) + operation.result(OPERATION_TIMEOUT_SECONDS) + + yield sample_database + + sample_database.drop() + return + + sample_database = sample_instance.database( + database_id, + ddl_statements=database_ddl, + ) + + if not sample_database.exists(): + operation = sample_database.create() + operation.result(OPERATION_TIMEOUT_SECONDS) + + yield sample_database + + sample_database.drop() + + +@pytest.fixture(scope="module") +def bit_reverse_sequence_database( + spanner_client, sample_instance, bit_reverse_sequence_database_id, database_dialect +): + if database_dialect == DatabaseDialect.POSTGRESQL: + bit_reverse_sequence_database = sample_instance.database( + bit_reverse_sequence_database_id, + database_dialect=DatabaseDialect.POSTGRESQL, + ) + + if not bit_reverse_sequence_database.exists(): + operation = bit_reverse_sequence_database.create() + operation.result(OPERATION_TIMEOUT_SECONDS) + + yield bit_reverse_sequence_database + + bit_reverse_sequence_database.drop() + return + + bit_reverse_sequence_database = sample_instance.database( + bit_reverse_sequence_database_id + ) + + if not bit_reverse_sequence_database.exists(): + operation = bit_reverse_sequence_database.create() + operation.result(OPERATION_TIMEOUT_SECONDS) + + yield bit_reverse_sequence_database + + bit_reverse_sequence_database.drop() + + +@pytest.fixture(scope="module") +def kms_key_name(spanner_client): + return "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format( + spanner_client.project, + "us-central1", + "spanner-test-keyring", + "spanner-test-cmek", + ) diff --git a/samples/generated/noxfile.py b/samples/generated/noxfile.py new file mode 100644 index 0000000000..6967925a83 --- /dev/null +++ b/samples/generated/noxfile.py @@ -0,0 +1,293 @@ +# Copyright 2024 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, 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==22.3.0" +ISORT_VERSION = "isort==5.10.1" + +# 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.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + +# 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 +# + + +# 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", + "--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") + else: + session.install("flake8", "flake8-annotations") + + args = FLAKE8_COMMON_ARGS + [ + ".", + ] + session.run("flake8", *args) + + +# +# Black +# + + +@nox.session +def blacken(session: nox.sessions.Session) -> None: + """Run black. Format code to uniform standard.""" + session.install(BLACK_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + session.run("black", *python_files) + + +# +# format = isort + black +# + + +@nox.session +def format(session: nox.sessions.Session) -> None: + """ + Run isort to sort imports. Then run black + to format code to uniform standard. + """ + session.install(BLACK_VERSION, ISORT_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + # Use the --fss option to sort imports using strict alphabetical order. + # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections + session.run("isort", "--fss", *python_files) + 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", recursive=True) + glob.glob( + "**/test_*.py", recursive=True + ) + test_list.extend(glob.glob("**/tests", recursive=True)) + + if len(test_list) == 0: + print("No tests found, skipping directory.") + return + + 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.""" + concurrent_args = [] + 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") + with open("requirements.txt") as rfile: + packages = rfile.read() + + 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") + with open("requirements-test.txt") as rtfile: + packages += rtfile.read() + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + if "pytest-parallel" in packages: + concurrent_args.extend(["--workers", "auto", "--tests-per-worker", "auto"]) + elif "pytest-xdist" in packages: + concurrent_args.extend(["-n", "auto"]) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs + concurrent_args), + # 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/generated/requirements-test.txt b/samples/generated/requirements-test.txt new file mode 100644 index 0000000000..bf07e9eaad --- /dev/null +++ b/samples/generated/requirements-test.txt @@ -0,0 +1,4 @@ +pytest==7.4.3 +pytest-dependency==0.5.1 +mock==5.1.0 +google-cloud-testutils==1.4.0 diff --git a/samples/generated/requirements.txt b/samples/generated/requirements.txt new file mode 100644 index 0000000000..36cf07c89a --- /dev/null +++ b/samples/generated/requirements.txt @@ -0,0 +1,2 @@ +google-cloud-spanner==3.41.0 +futures==3.4.0; python_version < "3" diff --git a/samples/generated/snippets.py b/samples/generated/snippets.py new file mode 100644 index 0000000000..d93e430bb1 --- /dev/null +++ b/samples/generated/snippets.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# Copyright 2024 Google, Inc. +# +# 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. + +"""This application demonstrates how to do basic operations using Cloud +Spanner. +For more information, see the README.rst under /spanner. +""" + +import time + +from google.cloud import spanner +from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin + +OPERATION_TIMEOUT_SECONDS = 240 + + +# [START spanner_create_instance] +def create_instance(instance_id): + """Creates an instance.""" + spanner_client = spanner.Client() + + config_name = "{}/instanceConfigs/regional-us-central1".format( + spanner_client.project_name + ) + + operation = spanner_client.instance_admin_api.create_instance( + parent="projects/{}".format(spanner_client.project), + instance_id=instance_id, + instance=spanner_instance_admin.Instance( + config=config_name, + display_name="This is a display name.", + node_count=1, + labels={ + "cloud_spanner_samples": "true", + "sample_name": "snippets-create_instance-explicit", + "created": str(int(time.time())), + }, + ), + ) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Created instance {}".format(instance_id)) + + +# [END spanner_create_instance] + + +# [START spanner_create_database_with_default_leader] +def create_database_with_default_leader(instance_id, database_id, default_leader): + """Creates a database with tables with a default leader.""" + spanner_client = spanner.Client() + operation = spanner_client.database_admin_api.create_database( + request={ + "parent": "projects/{}/instances/{}".format( + spanner_client.project, instance_id + ), + "create_statement": "CREATE DATABASE {}".format(database_id), + "extra_statements": [ + """CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)""", + """CREATE TABLE Albums ( + SingerId INT64 NOT NULL, + AlbumId INT64 NOT NULL, + AlbumTitle STRING(MAX) + ) PRIMARY KEY (SingerId, AlbumId), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE""", + "ALTER DATABASE {}" + " SET OPTIONS (default_leader = '{}')".format( + database_id, default_leader + ), + ], + }, + ) + + print("Waiting for operation to complete...") + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Database {} created with default leader {}".format( + database.name, database.default_leader + ) + ) + + +# [END spanner_create_database_with_default_leader] diff --git a/samples/generated/snippets_test.py b/samples/generated/snippets_test.py new file mode 100644 index 0000000000..2e655f873f --- /dev/null +++ b/samples/generated/snippets_test.py @@ -0,0 +1,207 @@ +# Copyright 2024 Google, Inc. + +# + +# Licensed under the Apache License, Version 2.0 (the "License"); + +# you may not use this file except in compliance with the License. + +# You may obtain a copy of the License at + +# + +# http://www.apache.org/licenses/LICENSE-2.0 + +# + +# Unless required by applicable law or agreed to in writing, software + +# distributed under the License is distributed on an "AS IS" BASIS, + +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +# See the License for the specific language governing permissions and + +# limitations under the License. + + +import uuid + + +from google.api_core import exceptions + +from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect + +import pytest + +from test_utils.retry import RetryErrors + + +import snippets + + +CREATE_TABLE_SINGERS = """\ + +CREATE TABLE Singers ( + + SingerId INT64 NOT NULL, + + FirstName STRING(1024), + + LastName STRING(1024), + + SingerInfo BYTES(MAX), + + FullName STRING(2048) AS ( + + ARRAY_TO_STRING([FirstName, LastName], " ") + + ) STORED + +) PRIMARY KEY (SingerId) + +""" + + +CREATE_TABLE_ALBUMS = """\ + +CREATE TABLE Albums ( + + SingerId INT64 NOT NULL, + + AlbumId INT64 NOT NULL, + + AlbumTitle STRING(MAX) + +) PRIMARY KEY (SingerId, AlbumId), + +INTERLEAVE IN PARENT Singers ON DELETE CASCADE + +""" + + +retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) + + +@pytest.fixture(scope="module") +def sample_name(): + return "snippets" + + +@pytest.fixture(scope="module") +def database_dialect(): + """Spanner dialect to be used for this sample. + + + + The dialect is used to initialize the dialect for the database. + + It can either be GoogleStandardSql or PostgreSql. + + """ + + return DatabaseDialect.GOOGLE_STANDARD_SQL + + +@pytest.fixture(scope="module") +def create_instance_id(): + """Id for the low-cost instance.""" + + return f"create-instance-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def lci_instance_id(): + """Id for the low-cost instance.""" + + return f"lci-instance-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def database_id(): + return f"test-db-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def create_database_id(): + return f"create-db-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def cmek_database_id(): + return f"cmek-db-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def default_leader_database_id(): + return f"leader_db_{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def database_ddl(): + """Sequence of DDL statements used to set up the database. + + + + Sample testcase modules can override as needed. + + """ + + return [CREATE_TABLE_SINGERS, CREATE_TABLE_ALBUMS] + + +@pytest.fixture(scope="module") +def default_leader(): + """Default leader for multi-region instances.""" + + return "us-east4" + + +@pytest.fixture(scope="module") +def user_managed_instance_config_name(spanner_client): + name = f"custom-python-samples-config-{uuid.uuid4().hex[:10]}" + + yield name + + snippets.delete_instance_config( + "{}/instanceConfigs/{}".format(spanner_client.project_name, name) + ) + + return + + +@pytest.fixture(scope="module") +def base_instance_config_id(spanner_client): + return "{}/instanceConfigs/{}".format(spanner_client.project_name, "nam7") + + +def test_create_instance_explicit(spanner_client, create_instance_id): + # Rather than re-use 'sample_isntance', we create a new instance, to + + # ensure that the 'create_instance' snippet is tested. + + retry_429(snippets.create_instance)(create_instance_id) + + instance = spanner_client.instance(create_instance_id) + + retry_429(instance.delete)() + + +def test_create_database_with_default_leader( + capsys, + multi_region_instance, + multi_region_instance_id, + default_leader_database_id, + default_leader, +): + retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) + + retry_429(snippets.create_database_with_default_leader)( + multi_region_instance_id, default_leader_database_id, default_leader + ) + + out, _ = capsys.readouterr() + + assert default_leader_database_id in out + + assert default_leader in out From e64bbc714fa4d580930656fab52a2d44008ffba9 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 22 Jan 2024 05:16:22 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- samples/generated/noxfile.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/samples/generated/noxfile.py b/samples/generated/noxfile.py index 6967925a83..483b559017 100644 --- a/samples/generated/noxfile.py +++ b/samples/generated/noxfile.py @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# 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. @@ -160,7 +160,6 @@ def blacken(session: nox.sessions.Session) -> None: # format = isort + black # - @nox.session def format(session: nox.sessions.Session) -> None: """ @@ -188,9 +187,7 @@ def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: # check for presence of tests - test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob( - "**/test_*.py", recursive=True - ) + test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob("**/test_*.py", recursive=True) test_list.extend(glob.glob("**/tests", recursive=True)) if len(test_list) == 0: @@ -212,7 +209,9 @@ def _session_tests( if os.path.exists("requirements-test.txt"): if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) else: session.install("-r", "requirements-test.txt") with open("requirements-test.txt") as rtfile: @@ -225,9 +224,9 @@ def _session_tests( post_install(session) if "pytest-parallel" in packages: - concurrent_args.extend(["--workers", "auto", "--tests-per-worker", "auto"]) + concurrent_args.extend(['--workers', 'auto', '--tests-per-worker', 'auto']) elif "pytest-xdist" in packages: - concurrent_args.extend(["-n", "auto"]) + concurrent_args.extend(['-n', 'auto']) session.run( "pytest", @@ -257,7 +256,7 @@ def py(session: nox.sessions.Session) -> None: def _get_repo_root() -> Optional[str]: - """Returns the root folder of the project.""" + """ 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): From 97d2d6362cb740366fcfd37998e5c3747ab63ad4 Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Mon, 22 Jan 2024 20:35:44 +0530 Subject: [PATCH 3/9] fix lint --- samples/generated/snippets_test.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/samples/generated/snippets_test.py b/samples/generated/snippets_test.py index 2e655f873f..e39ca95c34 100644 --- a/samples/generated/snippets_test.py +++ b/samples/generated/snippets_test.py @@ -1,29 +1,24 @@ -# Copyright 2024 Google, Inc. +#!/usr/bin/env python +# Copyright 2024 Google, Inc. # - # 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. +"""This application demonstrates how to do basic operations using Cloud +Spanner. +For more information, see the README.rst under /spanner. +""" + import uuid From 19d596a96e6b33061c0f595322da7d452912e75d Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Thu, 25 Jan 2024 22:59:29 +0530 Subject: [PATCH 4/9] incorporate suggestions --- samples/generated/conftest.py | 293 ------------------ samples/generated/noxfile.py | 292 ----------------- samples/generated/requirements-test.txt | 4 - samples/generated/requirements.txt | 2 - .../{generated => samples/admin}/snippets.py | 20 +- .../admin}/snippets_test.py | 0 6 files changed, 10 insertions(+), 601 deletions(-) delete mode 100644 samples/generated/conftest.py delete mode 100644 samples/generated/noxfile.py delete mode 100644 samples/generated/requirements-test.txt delete mode 100644 samples/generated/requirements.txt rename samples/{generated => samples/admin}/snippets.py (85%) rename samples/{generated => samples/admin}/snippets_test.py (100%) diff --git a/samples/generated/conftest.py b/samples/generated/conftest.py deleted file mode 100644 index 75e4082a20..0000000000 --- a/samples/generated/conftest.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2024 Google LLC 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. -""" Shared pytest fixtures.""" - -import time -import uuid - -from google.api_core import exceptions - -from google.cloud import spanner_admin_database_v1 -from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect -from google.cloud.spanner_v1 import backup -from google.cloud.spanner_v1 import client -from google.cloud.spanner_v1 import database -from google.cloud.spanner_v1 import instance -import pytest -from test_utils import retry - -INSTANCE_CREATION_TIMEOUT = 560 # seconds - -OPERATION_TIMEOUT_SECONDS = 120 # seconds - -retry_429 = retry.RetryErrors(exceptions.ResourceExhausted, delay=15) - - -@pytest.fixture(scope="module") -def sample_name(): - """Sample testcase modules must define this fixture. - - The name is used to label the instance created by the sample, to - aid in debugging leaked instances. - """ - raise NotImplementedError("Define 'sample_name' fixture in sample test driver") - - -@pytest.fixture(scope="module") -def database_dialect(): - """Database dialect to be used for this sample. - - The dialect is used to initialize the dialect for the database. - It can either be GoogleStandardSql or PostgreSql. - """ - # By default, we consider GOOGLE_STANDARD_SQL dialect. Other specific tests - # can override this if required. - return DatabaseDialect.GOOGLE_STANDARD_SQL - - -@pytest.fixture(scope="session") -def spanner_client(): - """Shared client used across all samples in a session.""" - return client.Client() - - -def scrub_instance_ignore_not_found(to_scrub): - """Helper for func:`cleanup_old_instances`""" - try: - for backup_pb in to_scrub.list_backups(): - backup.Backup.from_pb(backup_pb, to_scrub).delete() - - retry_429(to_scrub.delete)() - except exceptions.NotFound: - pass - - -@pytest.fixture(scope="session") -def cleanup_old_instances(spanner_client): - """Delete instances, created by samples, that are older than an hour.""" - cutoff = int(time.time()) - 1 * 60 * 60 - instance_filter = "labels.cloud_spanner_samples:true" - - for instance_pb in spanner_client.list_instances(filter_=instance_filter): - inst = instance.Instance.from_pb(instance_pb, spanner_client) - - if "created" in inst.labels: - create_time = int(inst.labels["created"]) - - if create_time <= cutoff: - scrub_instance_ignore_not_found(inst) - - -@pytest.fixture(scope="module") -def instance_id(): - """Unique id for the instance used in samples.""" - return f"test-instance-{uuid.uuid4().hex[:10]}" - - -@pytest.fixture(scope="module") -def multi_region_instance_id(): - """Unique id for the multi-region instance used in samples.""" - return f"multi-instance-{uuid.uuid4().hex[:10]}" - - -@pytest.fixture(scope="module") -def instance_config(spanner_client): - return "{}/instanceConfigs/{}".format( - spanner_client.project_name, "regional-us-central1" - ) - - -@pytest.fixture(scope="module") -def multi_region_instance_config(spanner_client): - return "{}/instanceConfigs/{}".format(spanner_client.project_name, "nam3") - - -@pytest.fixture(scope="module") -def sample_instance( - spanner_client, - cleanup_old_instances, - instance_id, - instance_config, - sample_name, -): - sample_instance = spanner_client.instance( - instance_id, - instance_config, - labels={ - "cloud_spanner_samples": "true", - "sample_name": sample_name, - "created": str(int(time.time())), - }, - ) - op = retry_429(sample_instance.create)() - op.result(INSTANCE_CREATION_TIMEOUT) # block until completion - - # Eventual consistency check - retry_found = retry.RetryResult(bool) - retry_found(sample_instance.exists)() - - yield sample_instance - - for database_pb in sample_instance.list_databases(): - database.Database.from_pb(database_pb, sample_instance).drop() - - for backup_pb in sample_instance.list_backups(): - backup.Backup.from_pb(backup_pb, sample_instance).delete() - - sample_instance.delete() - - -@pytest.fixture(scope="module") -def multi_region_instance( - spanner_client, - cleanup_old_instances, - multi_region_instance_id, - multi_region_instance_config, - sample_name, -): - multi_region_instance = spanner_client.instance( - multi_region_instance_id, - multi_region_instance_config, - labels={ - "cloud_spanner_samples": "true", - "sample_name": sample_name, - "created": str(int(time.time())), - }, - ) - op = retry_429(multi_region_instance.create)() - op.result(INSTANCE_CREATION_TIMEOUT) # block until completion - - # Eventual consistency check - retry_found = retry.RetryResult(bool) - retry_found(multi_region_instance.exists)() - - yield multi_region_instance - - for database_pb in multi_region_instance.list_databases(): - database.Database.from_pb(database_pb, multi_region_instance).drop() - - for backup_pb in multi_region_instance.list_backups(): - backup.Backup.from_pb(backup_pb, multi_region_instance).delete() - - multi_region_instance.delete() - - -@pytest.fixture(scope="module") -def database_id(): - """Id for the database used in samples. - - Sample testcase modules can override as needed. - """ - return "my-database-id" - - -@pytest.fixture(scope="module") -def bit_reverse_sequence_database_id(): - """Id for the database used in bit reverse sequence samples. - - Sample testcase modules can override as needed. - """ - return "sequence-database-id" - - -@pytest.fixture(scope="module") -def database_ddl(): - """Sequence of DDL statements used to set up the database. - - Sample testcase modules can override as needed. - """ - return [] - - -@pytest.fixture(scope="module") -def sample_database( - spanner_client, sample_instance, database_id, database_ddl, database_dialect -): - if database_dialect == DatabaseDialect.POSTGRESQL: - sample_database = sample_instance.database( - database_id, - database_dialect=DatabaseDialect.POSTGRESQL, - ) - - if not sample_database.exists(): - operation = sample_database.create() - operation.result(OPERATION_TIMEOUT_SECONDS) - - request = spanner_admin_database_v1.UpdateDatabaseDdlRequest( - database=sample_database.name, - statements=database_ddl, - ) - - operation = spanner_client.database_admin_api.update_database_ddl(request) - operation.result(OPERATION_TIMEOUT_SECONDS) - - yield sample_database - - sample_database.drop() - return - - sample_database = sample_instance.database( - database_id, - ddl_statements=database_ddl, - ) - - if not sample_database.exists(): - operation = sample_database.create() - operation.result(OPERATION_TIMEOUT_SECONDS) - - yield sample_database - - sample_database.drop() - - -@pytest.fixture(scope="module") -def bit_reverse_sequence_database( - spanner_client, sample_instance, bit_reverse_sequence_database_id, database_dialect -): - if database_dialect == DatabaseDialect.POSTGRESQL: - bit_reverse_sequence_database = sample_instance.database( - bit_reverse_sequence_database_id, - database_dialect=DatabaseDialect.POSTGRESQL, - ) - - if not bit_reverse_sequence_database.exists(): - operation = bit_reverse_sequence_database.create() - operation.result(OPERATION_TIMEOUT_SECONDS) - - yield bit_reverse_sequence_database - - bit_reverse_sequence_database.drop() - return - - bit_reverse_sequence_database = sample_instance.database( - bit_reverse_sequence_database_id - ) - - if not bit_reverse_sequence_database.exists(): - operation = bit_reverse_sequence_database.create() - operation.result(OPERATION_TIMEOUT_SECONDS) - - yield bit_reverse_sequence_database - - bit_reverse_sequence_database.drop() - - -@pytest.fixture(scope="module") -def kms_key_name(spanner_client): - return "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format( - spanner_client.project, - "us-central1", - "spanner-test-keyring", - "spanner-test-cmek", - ) diff --git a/samples/generated/noxfile.py b/samples/generated/noxfile.py deleted file mode 100644 index 483b559017..0000000000 --- a/samples/generated/noxfile.py +++ /dev/null @@ -1,292 +0,0 @@ -# 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, 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==22.3.0" -ISORT_VERSION = "isort==5.10.1" - -# 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.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - -# 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 -# - - -# 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", - "--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") - else: - session.install("flake8", "flake8-annotations") - - args = FLAKE8_COMMON_ARGS + [ - ".", - ] - session.run("flake8", *args) - - -# -# Black -# - - -@nox.session -def blacken(session: nox.sessions.Session) -> None: - """Run black. Format code to uniform standard.""" - session.install(BLACK_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - session.run("black", *python_files) - - -# -# format = isort + black -# - -@nox.session -def format(session: nox.sessions.Session) -> None: - """ - Run isort to sort imports. Then run black - to format code to uniform standard. - """ - session.install(BLACK_VERSION, ISORT_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - # Use the --fss option to sort imports using strict alphabetical order. - # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections - session.run("isort", "--fss", *python_files) - 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", recursive=True) + glob.glob("**/test_*.py", recursive=True) - test_list.extend(glob.glob("**/tests", recursive=True)) - - if len(test_list) == 0: - print("No tests found, skipping directory.") - return - - 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.""" - concurrent_args = [] - 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") - with open("requirements.txt") as rfile: - packages = rfile.read() - - 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") - with open("requirements-test.txt") as rtfile: - packages += rtfile.read() - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - if "pytest-parallel" in packages: - concurrent_args.extend(['--workers', 'auto', '--tests-per-worker', 'auto']) - elif "pytest-xdist" in packages: - concurrent_args.extend(['-n', 'auto']) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs + concurrent_args), - # 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/generated/requirements-test.txt b/samples/generated/requirements-test.txt deleted file mode 100644 index bf07e9eaad..0000000000 --- a/samples/generated/requirements-test.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest==7.4.3 -pytest-dependency==0.5.1 -mock==5.1.0 -google-cloud-testutils==1.4.0 diff --git a/samples/generated/requirements.txt b/samples/generated/requirements.txt deleted file mode 100644 index 36cf07c89a..0000000000 --- a/samples/generated/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -google-cloud-spanner==3.41.0 -futures==3.4.0; python_version < "3" diff --git a/samples/generated/snippets.py b/samples/samples/admin/snippets.py similarity index 85% rename from samples/generated/snippets.py rename to samples/samples/admin/snippets.py index d93e430bb1..24a0473c49 100644 --- a/samples/generated/snippets.py +++ b/samples/samples/admin/snippets.py @@ -72,17 +72,17 @@ def create_database_with_default_leader(instance_id, database_id, default_leader "create_statement": "CREATE DATABASE {}".format(database_id), "extra_statements": [ """CREATE TABLE Singers ( - SingerId INT64 NOT NULL, - FirstName STRING(1024), - LastName STRING(1024), - SingerInfo BYTES(MAX) - ) PRIMARY KEY (SingerId)""", + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)""", """CREATE TABLE Albums ( - SingerId INT64 NOT NULL, - AlbumId INT64 NOT NULL, - AlbumTitle STRING(MAX) - ) PRIMARY KEY (SingerId, AlbumId), - INTERLEAVE IN PARENT Singers ON DELETE CASCADE""", + SingerId INT64 NOT NULL, + AlbumId INT64 NOT NULL, + AlbumTitle STRING(MAX) + ) PRIMARY KEY (SingerId, AlbumId), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE""", "ALTER DATABASE {}" " SET OPTIONS (default_leader = '{}')".format( database_id, default_leader diff --git a/samples/generated/snippets_test.py b/samples/samples/admin/snippets_test.py similarity index 100% rename from samples/generated/snippets_test.py rename to samples/samples/admin/snippets_test.py From b54849c5e068702db945bd3ed49fba87961943f6 Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Thu, 25 Jan 2024 23:17:32 +0530 Subject: [PATCH 5/9] rename tests --- samples/samples/admin/{snippets.py => samples.py} | 0 samples/samples/admin/{snippets_test.py => samples_test.py} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename samples/samples/admin/{snippets.py => samples.py} (100%) rename samples/samples/admin/{snippets_test.py => samples_test.py} (99%) diff --git a/samples/samples/admin/snippets.py b/samples/samples/admin/samples.py similarity index 100% rename from samples/samples/admin/snippets.py rename to samples/samples/admin/samples.py diff --git a/samples/samples/admin/snippets_test.py b/samples/samples/admin/samples_test.py similarity index 99% rename from samples/samples/admin/snippets_test.py rename to samples/samples/admin/samples_test.py index e39ca95c34..56f9ef7e63 100644 --- a/samples/samples/admin/snippets_test.py +++ b/samples/samples/admin/samples_test.py @@ -32,7 +32,7 @@ from test_utils.retry import RetryErrors -import snippets +import samples CREATE_TABLE_SINGERS = """\ From ec793813a5ec1f900edc4024e622a4d2233714fb Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Thu, 25 Jan 2024 23:23:07 +0530 Subject: [PATCH 6/9] fix lint --- samples/samples/admin/samples_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/samples/admin/samples_test.py b/samples/samples/admin/samples_test.py index 56f9ef7e63..bea10f86be 100644 --- a/samples/samples/admin/samples_test.py +++ b/samples/samples/admin/samples_test.py @@ -175,7 +175,7 @@ def test_create_instance_explicit(spanner_client, create_instance_id): # ensure that the 'create_instance' snippet is tested. - retry_429(snippets.create_instance)(create_instance_id) + retry_429(samples.create_instance)(create_instance_id) instance = spanner_client.instance(create_instance_id) @@ -191,7 +191,7 @@ def test_create_database_with_default_leader( ): retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) - retry_429(snippets.create_database_with_default_leader)( + retry_429(samples.create_database_with_default_leader)( multi_region_instance_id, default_leader_database_id, default_leader ) From 2cba75354e45c93184c477ae6a00e5bbd2c2541b Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Fri, 26 Jan 2024 09:39:35 +0530 Subject: [PATCH 7/9] fix failures --- samples/samples/admin/samples_test.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/samples/samples/admin/samples_test.py b/samples/samples/admin/samples_test.py index bea10f86be..11211ba296 100644 --- a/samples/samples/admin/samples_test.py +++ b/samples/samples/admin/samples_test.py @@ -152,19 +152,6 @@ def default_leader(): return "us-east4" -@pytest.fixture(scope="module") -def user_managed_instance_config_name(spanner_client): - name = f"custom-python-samples-config-{uuid.uuid4().hex[:10]}" - - yield name - - snippets.delete_instance_config( - "{}/instanceConfigs/{}".format(spanner_client.project_name, name) - ) - - return - - @pytest.fixture(scope="module") def base_instance_config_id(spanner_client): return "{}/instanceConfigs/{}".format(spanner_client.project_name, "nam7") From d486b13c1615df1c70645ca0a399bdaace5f1496 Mon Sep 17 00:00:00 2001 From: Sri Harsha CH Date: Sat, 27 Jan 2024 11:33:14 +0000 Subject: [PATCH 8/9] chore(spanner): fix formatting --- samples/samples/admin/samples_test.py | 46 --------------------------- 1 file changed, 46 deletions(-) diff --git a/samples/samples/admin/samples_test.py b/samples/samples/admin/samples_test.py index 11211ba296..1fe8e0bd17 100644 --- a/samples/samples/admin/samples_test.py +++ b/samples/samples/admin/samples_test.py @@ -19,62 +19,36 @@ For more information, see the README.rst under /spanner. """ - import uuid - from google.api_core import exceptions - from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect - import pytest - from test_utils.retry import RetryErrors - import samples - CREATE_TABLE_SINGERS = """\ - CREATE TABLE Singers ( - SingerId INT64 NOT NULL, - FirstName STRING(1024), - LastName STRING(1024), - SingerInfo BYTES(MAX), - FullName STRING(2048) AS ( - ARRAY_TO_STRING([FirstName, LastName], " ") - ) STORED - ) PRIMARY KEY (SingerId) - """ - CREATE_TABLE_ALBUMS = """\ - CREATE TABLE Albums ( - SingerId INT64 NOT NULL, - AlbumId INT64 NOT NULL, - AlbumTitle STRING(MAX) - ) PRIMARY KEY (SingerId, AlbumId), - INTERLEAVE IN PARENT Singers ON DELETE CASCADE - """ - retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) @@ -87,28 +61,21 @@ def sample_name(): def database_dialect(): """Spanner dialect to be used for this sample. - - The dialect is used to initialize the dialect for the database. - It can either be GoogleStandardSql or PostgreSql. - """ - return DatabaseDialect.GOOGLE_STANDARD_SQL @pytest.fixture(scope="module") def create_instance_id(): """Id for the low-cost instance.""" - return f"create-instance-{uuid.uuid4().hex[:10]}" @pytest.fixture(scope="module") def lci_instance_id(): """Id for the low-cost instance.""" - return f"lci-instance-{uuid.uuid4().hex[:10]}" @@ -136,19 +103,14 @@ def default_leader_database_id(): def database_ddl(): """Sequence of DDL statements used to set up the database. - - Sample testcase modules can override as needed. - """ - return [CREATE_TABLE_SINGERS, CREATE_TABLE_ALBUMS] @pytest.fixture(scope="module") def default_leader(): """Default leader for multi-region instances.""" - return "us-east4" @@ -159,13 +121,9 @@ def base_instance_config_id(spanner_client): def test_create_instance_explicit(spanner_client, create_instance_id): # Rather than re-use 'sample_isntance', we create a new instance, to - # ensure that the 'create_instance' snippet is tested. - retry_429(samples.create_instance)(create_instance_id) - instance = spanner_client.instance(create_instance_id) - retry_429(instance.delete)() @@ -177,13 +135,9 @@ def test_create_database_with_default_leader( default_leader, ): retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) - retry_429(samples.create_database_with_default_leader)( multi_region_instance_id, default_leader_database_id, default_leader ) - out, _ = capsys.readouterr() - assert default_leader_database_id in out - assert default_leader in out From af6695119cd5784031b8bfa884e22eca7807c3b9 Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Mon, 29 Jan 2024 10:50:35 +0530 Subject: [PATCH 9/9] incorporate suggesitons --- samples/samples/admin/samples.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/samples/admin/samples.py b/samples/samples/admin/samples.py index 24a0473c49..7a7afac93c 100644 --- a/samples/samples/admin/samples.py +++ b/samples/samples/admin/samples.py @@ -23,6 +23,7 @@ from google.cloud import spanner from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin +from google.cloud.spanner_admin_database_v1.types import spanner_database_admin OPERATION_TIMEOUT_SECONDS = 240 @@ -65,12 +66,12 @@ def create_database_with_default_leader(instance_id, database_id, default_leader """Creates a database with tables with a default leader.""" spanner_client = spanner.Client() operation = spanner_client.database_admin_api.create_database( - request={ - "parent": "projects/{}/instances/{}".format( + request=spanner_database_admin.CreateDatabaseRequest( + parent="projects/{}/instances/{}".format( spanner_client.project, instance_id ), - "create_statement": "CREATE DATABASE {}".format(database_id), - "extra_statements": [ + create_statement="CREATE DATABASE {}".format(database_id), + extra_statements=[ """CREATE TABLE Singers ( SingerId INT64 NOT NULL, FirstName STRING(1024), @@ -88,7 +89,7 @@ def create_database_with_default_leader(instance_id, database_id, default_leader database_id, default_leader ), ], - }, + ) ) print("Waiting for operation to complete...")