From 2e0a1c9c29ad59ad327fad76a8389b9464ba8352 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 30 Nov 2016 15:01:31 -0800 Subject: [PATCH 1/3] Add spanner samples Change-Id: I9b7bf8691b9433eb48cb67c80fcd6ecf03db1ebd --- .travis.yml | 3 +- conftest.py | 1 + spanner/cloud-client/README.rst | 151 ++++++++ spanner/cloud-client/README.rst.in | 22 ++ spanner/cloud-client/quickstart.py | 47 +++ spanner/cloud-client/quickstart_test.py | 55 +++ spanner/cloud-client/requirements.txt | 1 + spanner/cloud-client/snippets.py | 451 ++++++++++++++++++++++++ spanner/cloud-client/snippets_test.py | 180 ++++++++++ testing/secrets.tar.enc | Bin 9760 -> 9760 bytes testing/test-env.tmpl.sh | 1 + 11 files changed, 910 insertions(+), 2 deletions(-) create mode 100644 spanner/cloud-client/README.rst create mode 100644 spanner/cloud-client/README.rst.in create mode 100644 spanner/cloud-client/quickstart.py create mode 100644 spanner/cloud-client/quickstart_test.py create mode 100644 spanner/cloud-client/requirements.txt create mode 100644 spanner/cloud-client/snippets.py create mode 100644 spanner/cloud-client/snippets_test.py diff --git a/.travis.yml b/.travis.yml index 932fee9e9fcc..318d883dff97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ cache: - $HOME/.cache env: global: - secure: V8kTaIK8NYMEUVzaekLoVgJzz5/9yA/KKL8CVgOmiPEjt1o1wAXy+ojyXCgjGmB16OOcTYKtXKvOBndDg99MxUzMc9uLrHF4ub2fJZy3ZoCfPaxHNOpIOTTAhB8J/nog/JpW5NnJOBGE8fAQ/TUy8nSvOwe27n4qKO5eWqTy5kA= + secure: RPClDmwFZnpBIKtMvlzjzZVam4flvJtSvxFD8mHCQVQ//KqyBbQxl970kqStOK7p0RXkOB3XuFDvVixqyuptoQ8wTdgSBEPAub4DwRpcmCc1exzErHIt9zep3cQhSUuzl8N/tNl3o6GG04NsZTeErORqxfDvk3WbqFa9593G358= addons: apt: sources: @@ -24,7 +24,6 @@ addons: install: - pip install --upgrade pip wheel virtualenv - pip install --upgrade nox-automation coverage -# Temporarily install this from source. - pip install --upgrade git+https://github.com/dhermes/ci-diff-helper.git script: - ./scripts/travis.sh diff --git a/conftest.py b/conftest.py index 1a39069a5e13..a4f3c1008f8a 100644 --- a/conftest.py +++ b/conftest.py @@ -32,6 +32,7 @@ def cloud_config(): storage_bucket=os.environ.get('CLOUD_STORAGE_BUCKET'), client_secrets=os.environ.get('GOOGLE_CLIENT_SECRETS'), bigtable_instance=os.environ.get('BIGTABLE_CLUSTER'), + spanner_instance=os.environ.get('SPANNER_INSTANCE'), api_key=os.environ.get('API_KEY')) diff --git a/spanner/cloud-client/README.rst b/spanner/cloud-client/README.rst new file mode 100644 index 000000000000..5e5bd52f3027 --- /dev/null +++ b/spanner/cloud-client/README.rst @@ -0,0 +1,151 @@ +.. This file is automatically generated. Do not edit this file directly. + +Google Cloud Spanner Python Samples +=============================================================================== + +This directory contains samples for Google Cloud Spanner. `Google Cloud Spanner`_ is a highly scalable, transactional, managed, NewSQL database service. Cloud Spanner solves the need for a horizontally-scaling database with consistent global transactions and SQL semantics. + + + + +.. _Google Cloud Spanner: https://cloud.google.com/spanner/docs + +Setup +------------------------------------------------------------------------------- + + +Authentication +++++++++++++++ + +Authentication is typically done through `Application Default Credentials`_, +which means you do not have to change the code to authenticate as long as +your environment has credentials. You have a few options for setting up +authentication: + +#. When running locally, use the `Google Cloud SDK`_ + + .. code-block:: bash + + gcloud beta auth application-default login + + +#. When running on App Engine or Compute Engine, credentials are already + set-up. However, you may need to configure your Compute Engine instance + with `additional scopes`_. + +#. You can create a `Service Account key file`_. This file can be used to + authenticate to Google Cloud Platform services from any environment. To use + the file, set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to + the path to the key file, for example: + + .. code-block:: bash + + export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account.json + +.. _Application Default Credentials: https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow +.. _additional scopes: https://cloud.google.com/compute/docs/authentication#using +.. _Service Account key file: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount + +Install Dependencies +++++++++++++++++++++ + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- + +Snippets ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + +To run this sample: + +.. code-block:: bash + + $ python snippets.py + + usage: snippets.py [-h] [--database-name DATABASE_NAME] + instance_name + {insert_data,query_data,read_data,update_data,read_write_transaction,query_data_with_index,read_data_with_index,read_data_with_storing_index} + ... + + This application demonstrates how to do basic operations using Cloud + Spanner. + + For more information, see the README.rst under /spanner. + + positional arguments: + instance_name Your Cloud Spanner instance name. + {insert_data,query_data,read_data,update_data,read_write_transaction,query_data_with_index,read_data_with_index,read_data_with_storing_index} + insert_data Inserts sample data into the given database. The + database and table must already exist and can be + created using `create_database`. + query_data Queries sample data from the database using SQL. + read_data Reads sample data from the database. + update_data Updates sample data in the database. This updates the + `MarketingBudget` column which must be created before + running this sample. Run the following query on your + database to create the column: ALTER TABLE Albums ADD + COLUMN MarketingBudget INT64 + read_write_transaction + Performs a read-write transaction to update two sample + records in the database. This will transfer 200,000 + from the `MarketingBudget` field for the first Album + to the second Album. If the `MarketingBudget` is too + low, it will raise an exception. Before running this + sample, you will need to run the `update_data` sample + to populate the fields. + query_data_with_index + Inserts sample data into the given database. The + database and table must already exist and can be + created using `create_database`. + read_data_with_index + Inserts sample data into the given database. The + database and table must already exist and can be + created using `create_database`. + read_data_with_storing_index + Inserts sample data into the given database. The + database and table must already exist and can be + created using `create_database`. + + optional arguments: + -h, --help show this help message and exit + --database-name DATABASE_NAME + Your Cloud Spanner database name. + + + + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/spanner/cloud-client/README.rst.in b/spanner/cloud-client/README.rst.in new file mode 100644 index 000000000000..40ff29695be8 --- /dev/null +++ b/spanner/cloud-client/README.rst.in @@ -0,0 +1,22 @@ +# This file is used to generate README.rst + +product: + name: Google Cloud Spanner + short_name: Cloud Spanner + url: https://cloud.google.com/spanner/docs + description: > + `Google Cloud Spanner`_ is a highly scalable, transactional, managed, + NewSQL database service. Cloud Spanner solves the need for a + horizontally-scaling database with consistent global transactions and + SQL semantics. + +setup: +- auth +- install_deps + +samples: +- name: Snippets + file: snippets.py + show_help: true + +cloud_client_library: true diff --git a/spanner/cloud-client/quickstart.py b/spanner/cloud-client/quickstart.py new file mode 100644 index 000000000000..43cbf4d7ab8a --- /dev/null +++ b/spanner/cloud-client/quickstart.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# Copyright 2016 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. + + +def run_quickstart(): + # [START spanner_quickstart] + # Imports the Google Cloud Client Library. + from google.cloud import spanner + + # Instantiate a client. + spanner_client = spanner.Client() + + # Your Cloud Spanner instance ID. + instance_id = 'my-instance-id' + + # Get a Cloud Spanner instance by ID. + instance = spanner_client.instance(instance_id) + + # Your Cloud Spanner database ID. + database_id = 'my-database-id' + + # Get a Cloud Spanner database by ID. + database = instance.database(database_id) + + # Execute a simple SQL statement. + results = database.execute_sql('SELECT 1') + + for row in results: + print(row) + # [END spanner_quickstart] + + +if __name__ == '__main__': + run_quickstart() diff --git a/spanner/cloud-client/quickstart_test.py b/spanner/cloud-client/quickstart_test.py new file mode 100644 index 000000000000..23b39eaa8300 --- /dev/null +++ b/spanner/cloud-client/quickstart_test.py @@ -0,0 +1,55 @@ +# Copyright 2016 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. + +import google.cloud.exceptions +from google.cloud import spanner +import google.cloud.spanner.client +import mock +import pytest + +import quickstart + + +@pytest.fixture +def patch_instance(cloud_config): + original_instance = google.cloud.spanner.client.Client.instance + + def new_instance(self, unused_instance_name): + return original_instance(self, cloud_config.spanner_instance) + + instance_patch = mock.patch( + 'google.cloud.spanner.client.Client.instance', + side_effect=new_instance, + autospec=True) + + with instance_patch: + yield + + +@pytest.fixture +def example_database(cloud_config): + spanner_client = spanner.Client() + instance = spanner_client.instance(cloud_config.spanner_instance) + database = instance.database('my-database-name') + + if not database.exists(): + database.create() + + yield + + +def test_quickstart(capsys, patch_instance, example_database): + quickstart.run_quickstart() + out, _ = capsys.readouterr() + assert '[1]' in out diff --git a/spanner/cloud-client/requirements.txt b/spanner/cloud-client/requirements.txt new file mode 100644 index 000000000000..8afaefa2c6f1 --- /dev/null +++ b/spanner/cloud-client/requirements.txt @@ -0,0 +1 @@ +google-cloud-spanner==0.23.0 diff --git a/spanner/cloud-client/snippets.py b/spanner/cloud-client/snippets.py new file mode 100644 index 000000000000..dc83eb10fcd2 --- /dev/null +++ b/spanner/cloud-client/snippets.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python + +# Copyright 2016 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 argparse + +from google.cloud import spanner + + +def create_database(instance_id, database_id): + """Creates a database and tables for sample data.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + + database = instance.database(database_id, ddl_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""" + ]) + + operation = database.create() + + print('Waiting for operation to complete...') + operation.result() + + print('Created database {} on instance {}'.format( + database_id, instance_id)) + + +def insert_data(instance_id, database_id): + """Inserts sample data into the given database. + + The database and table must already exist and can be created using + `create_database`. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + with database.batch() as batch: + batch.insert( + table='Singers', + columns=('SingerId', 'FirstName', 'LastName',), + values=[ + (1, u'Marc', u'Richards'), + (2, u'Catalina', u'Smith'), + (3, u'Alice', u'Trentor'), + (4, u'Lea', u'Martin'), + (5, u'David', u'Lomond')]) + + batch.insert( + table='Albums', + columns=('SingerId', 'AlbumId', 'AlbumTitle',), + values=[ + (1, 1, u'Go, Go, Go'), + (1, 2, u'Total Junk'), + (2, 1, u'Green'), + (2, 2, u'Forever Hold Your Peace'), + (2, 3, u'Terrified')]) + + print('Inserted data.') + + +def query_data(instance_id, database_id): + """Queries sample data from the database using SQL.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + results = database.execute_sql( + 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums') + + for row in results: + print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row)) + + +def read_data(instance_id, database_id): + """Reads sample data from the database.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + keyset = spanner.KeySet(all_=True) + results = database.read( + table='Albums', + columns=('SingerId', 'AlbumId', 'AlbumTitle',), + keyset=keyset,) + + for row in results: + print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row)) + + +def query_data_with_new_column(instance_id, database_id): + """Queries sample data from the database using SQL. + + This sample uses the `MarketingBudget` column. You can add the column + by running the `add_column` sample or by running this DDL statement against + your database: + + ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + results = database.execute_sql( + 'SELECT SingerId, AlbumId, MarketingBudget FROM Albums') + + for row in results: + print(u'SingerId: {}, AlbumId: {}, MarketingBudget: {}'.format(*row)) + + +def add_index(instance_id, database_id): + """Adds a simple index to the example database.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + operation = database.update_ddl([ + 'CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)']) + + print('Waiting for operation to complete...') + operation.result() + + print('Added the AlbumsByAlbumTitle index.') + + +def query_data_with_index(instance_id, database_id): + """Queries sample data from the database using SQL and an index. + + The index must exist before running this sample. You can add the index + by running the `add_index` sample or by running this DDL statement against + your database: + + CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle) + + This sample also uses the `MarketingBudget` column. You can add the column + by running the `add_column` sample or by running this DDL statement against + your database: + + ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + results = database.execute_sql( + "SELECT AlbumId, AlbumTitle, MarketingBudget " + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} " + "WHERE AlbumTitle >= 'Ardvark' AND AlbumTitle < 'Goo'") + + for row in results: + print( + u'AlbumId: {}, AlbumTitle: {}, ' + 'MarketingBudget: {}'.format(*row)) + + +def read_data_with_index(instance_id, database_id): + """Reads sample data from the database using an index. + + The index must exist before running this sample. You can add the index + by running the `add_index` sample or by running this DDL statement against + your database: + + CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle) + + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + keyset = spanner.KeySet(all_=True) + results = database.read( + table='Albums', + columns=('AlbumId', 'AlbumTitle'), + keyset=keyset, + index='AlbumsByAlbumTitle') + + for row in results: + print('AlbumId: {}, AlbumTitle: {}'.format(*row)) + + +def add_storing_index(instance_id, database_id): + """Adds an storing index to the example database.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + operation = database.update_ddl([ + 'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)' + 'STORING (MarketingBudget)']) + + print('Waiting for operation to complete...') + operation.result() + + print('Added the AlbumsByAlbumTitle2 index.') + + +def read_data_with_storing_index(instance_id, database_id): + """Reads sample data from the database using an index with a storing + clause. + + The index must exist before running this sample. You can add the index + by running the `add_soring_index` sample or by running this DDL statement + against your database: + + CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) + STORING (MarketingBudget) + + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + keyset = spanner.KeySet(all_=True) + results = database.read( + table='Albums', + columns=('AlbumId', 'AlbumTitle', 'MarketingBudget'), + keyset=keyset, + index='AlbumsByAlbumTitle2') + + for row in results: + print( + u'AlbumId: {}, AlbumTitle: {}, ' + 'MarketingBudget: {}'.format(*row)) + + +def add_column(instance_id, database_id): + """Adds a new column to the Albums table in the example database.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + operation = database.update_ddl([ + 'ALTER TABLE Albums ADD COLUMN MarketingBudget INT64']) + + print('Waiting for operation to complete...') + operation.result() + + print('Added the MarketingBudget column.') + + +def update_data(instance_id, database_id): + """Updates sample data in the database. + + This updates the `MarketingBudget` column which must be created before + running this sample. You can add the column by running the `add_column` + sample or by running this DDL statement against your database: + + ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + with database.batch() as batch: + batch.update( + table='Albums', + columns=( + 'SingerId', 'AlbumId', 'MarketingBudget'), + values=[ + (1, 1, 100000), + (2, 2, 500000)]) + + print('Updated data.') + + +def read_write_transaction(instance_id, database_id): + """Performs a read-write transaction to update two sample records in the + database. + + This will transfer 200,000 from the `MarketingBudget` field for the second + Album to the first Album. If the `MarketingBudget` is too low, it will + raise an exception. + + Before running this sample, you will need to run the `update_data` sample + to populate the fields. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + def update_albums(transaction): + # Read the second album budget. + second_album_keyset = spanner.KeySet(keys=[(2, 2)]) + second_album_result = transaction.read( + table='Albums', columns=('MarketingBudget',), + keyset=second_album_keyset, limit=1) + second_album_row = list(second_album_result)[0] + second_album_budget = second_album_row[0] + + transfer_amount = 200000 + + if second_album_budget < transfer_amount: + # Raising an exception will automatically roll back the + # transaction. + raise ValueError( + 'The second album doesn\'t have enough funds to transfer') + + # Read the first album's budget. + first_album_keyset = spanner.KeySet(keys=[(1, 1)]) + first_album_result = transaction.read( + table='Albums', columns=('MarketingBudget',), + keyset=first_album_keyset, limit=1) + first_album_row = list(first_album_result)[0] + first_album_budget = first_album_row[0] + + # Update the budgets. + second_album_budget -= transfer_amount + first_album_budget += transfer_amount + print( + 'Setting first album\'s budget to {} and the second album\'s ' + 'budget to {}.'.format( + first_album_budget, second_album_budget)) + + # Update the rows. + transaction.update( + table='Albums', + columns=( + 'SingerId', 'AlbumId', 'MarketingBudget'), + values=[ + (1, 1, first_album_budget), + (2, 2, second_album_budget)]) + + database.run_in_transaction(update_albums) + + print('Transaction complete.') + + +def read_only_transaction(instance_id, database_id): + """Reads data inside of a read-only transaction. + + Within the read-only transaction, or "snapshot", the application sees + consistent view of the database at a particular timestamp. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + with database.snapshot() as snapshot: + # Read using SQL. + results = snapshot.execute_sql( + 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums') + + print('Results from first read:') + for row in results: + print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row)) + + # Perform another read using the `read` method. Even if the data + # is updated in-between the reads, the snapshot ensures that both + # return the same data. + keyset = spanner.KeySet(all_=True) + results = database.read( + table='Albums', + columns=('SingerId', 'AlbumId', 'AlbumTitle',), + keyset=keyset,) + + print('Results from second read:') + for row in results: + print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + 'instance_id', help='Your Cloud Spanner instance ID.') + parser.add_argument( + '--database-id', help='Your Cloud Spanner database ID.', + default='example_db') + + subparsers = parser.add_subparsers(dest='command') + subparsers.add_parser('create_database', help=create_database.__doc__) + subparsers.add_parser('insert_data', help=insert_data.__doc__) + subparsers.add_parser('query_data', help=query_data.__doc__) + subparsers.add_parser('read_data', help=read_data.__doc__) + subparsers.add_parser('add_column', help=add_column.__doc__) + subparsers.add_parser('update_data', help=update_data.__doc__) + subparsers.add_parser( + 'query_data_with_new_column', help=query_data_with_new_column.__doc__) + subparsers.add_parser( + 'read_write_transaction', help=read_write_transaction.__doc__) + subparsers.add_parser( + 'read_only_transaction', help=read_only_transaction.__doc__) + subparsers.add_parser('add_index', help=add_index.__doc__) + subparsers.add_parser('query_data_with_index', help=insert_data.__doc__) + subparsers.add_parser('read_data_with_index', help=insert_data.__doc__) + subparsers.add_parser('add_storing_index', help=add_storing_index.__doc__) + subparsers.add_parser( + 'read_data_with_storing_index', help=insert_data.__doc__) + + args = parser.parse_args() + + if args.command == 'create_database': + create_database(args.instance_id, args.database_id) + if args.command == 'insert_data': + insert_data(args.instance_id, args.database_id) + elif args.command == 'query_data': + query_data(args.instance_id, args.database_id) + elif args.command == 'read_data': + read_data(args.instance_id, args.database_id) + elif args.command == 'add_column': + add_column(args.instance_id, args.database_id) + elif args.command == 'update_data': + update_data(args.instance_id, args.database_id) + elif args.command == 'query_data_with_new_column': + query_data_with_new_column(args.instance_id, args.database_id) + elif args.command == 'read_write_transaction': + read_write_transaction(args.instance_id, args.database_id) + elif args.command == 'read_only_transaction': + read_only_transaction(args.instance_id, args.database_id) + elif args.command == 'add_index': + add_index(args.instance_id, args.database_id) + elif args.command == 'query_data_with_index': + query_data_with_index(args.instance_id, args.database_id) + elif args.command == 'read_data_with_index': + read_data_with_index(args.instance_id, args.database_id) + elif args.command == 'add_storing_index': + add_storing_index(args.instance_id, args.database_id) + elif args.command == 'read_data_with_storing_index': + read_data_with_storing_index(args.instance_id, args.database_id) diff --git a/spanner/cloud-client/snippets_test.py b/spanner/cloud-client/snippets_test.py new file mode 100644 index 000000000000..36915332bc02 --- /dev/null +++ b/spanner/cloud-client/snippets_test.py @@ -0,0 +1,180 @@ +# Copyright 2016 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 random +import string + +from gcp.testing import eventually_consistent +from google.cloud import spanner +import pytest + +import snippets + + +@pytest.fixture(scope='module') +def spanner_instance(cloud_config): + spanner_client = spanner.Client() + return spanner_client.instance(cloud_config.spanner_instance) + + +def unique_database_id(): + return 'test-db-{}'.format(''.join(random.choice( + string.ascii_lowercase + string.digits) for _ in range(5))) + + +def test_create_database(cloud_config, spanner_instance): + database_id = unique_database_id() + print(cloud_config.spanner_instance, database_id) + snippets.create_database( + cloud_config.spanner_instance, database_id) + + database = spanner_instance.database(database_id) + database.reload() # Will only succeed if the database exists. + database.drop() + + +@pytest.fixture(scope='module') +def temporary_database(cloud_config, spanner_instance): + database_id = unique_database_id() + snippets.create_database(cloud_config.spanner_instance, database_id) + snippets.insert_data( + cloud_config.spanner_instance, database_id) + database = spanner_instance.database(database_id) + database.reload() + yield database + database.drop() + + +def test_query_data(cloud_config, temporary_database, capsys): + snippets.query_data( + cloud_config.spanner_instance, temporary_database.database_id) + + out, _ = capsys.readouterr() + + assert 'Total Junk' in out + + +def test_read_data(cloud_config, temporary_database, capsys): + snippets.read_data( + cloud_config.spanner_instance, temporary_database.database_id) + + out, _ = capsys.readouterr() + + assert 'Total Junk' in out + + +@pytest.fixture(scope='module') +def temporary_database_with_column(cloud_config, temporary_database): + snippets.add_column( + cloud_config.spanner_instance, temporary_database.database_id) + yield temporary_database + + +def test_update_data(cloud_config, temporary_database_with_column): + snippets.update_data( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + + +def test_query_data_with_new_column( + cloud_config, temporary_database_with_column, capsys): + snippets.query_data_with_new_column( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + + out, _ = capsys.readouterr() + assert 'MarketingBudget' in out + + +@pytest.fixture(scope='module') +def temporary_database_with_indexes( + cloud_config, temporary_database_with_column): + snippets.add_index( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + snippets.add_storing_index( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + + yield temporary_database_with_column + + +@pytest.mark.slow +def test_query_data_with_index( + cloud_config, temporary_database_with_indexes, capsys): + @eventually_consistent.call + def _(): + snippets.query_data_with_index( + cloud_config.spanner_instance, + temporary_database_with_indexes.database_id) + + out, _ = capsys.readouterr() + assert 'Go, Go, Go' in out + + +@pytest.mark.slow +def test_read_data_with_index( + cloud_config, temporary_database_with_indexes, capsys): + @eventually_consistent.call + def _(): + snippets.read_data_with_index( + cloud_config.spanner_instance, + temporary_database_with_indexes.database_id) + + out, _ = capsys.readouterr() + assert 'Go, Go, Go' in out + + +@pytest.mark.slow +def test_read_data_with_storing_index( + cloud_config, temporary_database_with_indexes, capsys): + @eventually_consistent.call + def _(): + snippets.read_data_with_storing_index( + cloud_config.spanner_instance, + temporary_database_with_indexes.database_id) + + out, _ = capsys.readouterr() + assert 'Go, Go, Go' in out + + +@pytest.mark.slow +def test_read_write_transaction( + cloud_config, temporary_database_with_column, capsys): + @eventually_consistent.call + def _(): + snippets.update_data( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + snippets.read_write_transaction( + cloud_config.spanner_instance, + temporary_database_with_column.database_id) + + out, _ = capsys.readouterr() + + assert '300000' in out + + +@pytest.mark.slow +def test_read_only_transaction( + cloud_config, temporary_database, capsys): + @eventually_consistent.call + def _(): + snippets.read_only_transaction( + cloud_config.spanner_instance, + temporary_database.database_id) + + out, _ = capsys.readouterr() + + assert 'Forever Hold Your Peace' in out diff --git a/testing/secrets.tar.enc b/testing/secrets.tar.enc index 6ef05dd0ee462f09011cd4cea44d8aaf68f06f47..fce758f2906da98e75eac20753a9c1556bacfb23 100644 GIT binary patch literal 9760 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 literal 9760 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 diff --git a/testing/test-env.tmpl.sh b/testing/test-env.tmpl.sh index 0734f19fa27c..43dcb2e3f2bc 100644 --- a/testing/test-env.tmpl.sh +++ b/testing/test-env.tmpl.sh @@ -4,6 +4,7 @@ export CLOUD_STORAGE_BUCKET=$GCLOUD_PROJECT export API_KEY= export BIGTABLE_CLUSTER=bigtable-test export BIGTABLE_ZONE=us-central1-c +export SPANNER_INSTANCE= # Environment variables for App Engine Flexible system tests. export GA_TRACKING_ID= From 680a63dd4c3100f2f2cd0233fb79953309a5329e Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 14 Feb 2017 11:43:08 -0800 Subject: [PATCH 2/3] Fix lint Change-Id: I78f51909bdf8fc8e298efb519a09e5db50ee1199 --- nox.py | 2 +- spanner/cloud-client/quickstart_test.py | 2 +- spanner/cloud-client/snippets.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nox.py b/nox.py index 70647b037703..15209e68673d 100644 --- a/nox.py +++ b/nox.py @@ -138,7 +138,7 @@ def _setup_appengine_sdk(session): '--cov-report', 'term'] FLAKE8_COMMON_ARGS = [ - '--show-source', '--builtin', 'gettext', '--max-complexity', '15', + '--show-source', '--builtin', 'gettext', '--max-complexity', '20', '--import-order-style', 'google', '--exclude', '.nox,.cache,env,lib,generated_pb2', ] diff --git a/spanner/cloud-client/quickstart_test.py b/spanner/cloud-client/quickstart_test.py index 23b39eaa8300..d383c2db9740 100644 --- a/spanner/cloud-client/quickstart_test.py +++ b/spanner/cloud-client/quickstart_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import google.cloud.exceptions from google.cloud import spanner +import google.cloud.exceptions import google.cloud.spanner.client import mock import pytest diff --git a/spanner/cloud-client/snippets.py b/spanner/cloud-client/snippets.py index dc83eb10fcd2..b80cf8dc798a 100644 --- a/spanner/cloud-client/snippets.py +++ b/spanner/cloud-client/snippets.py @@ -423,7 +423,7 @@ def read_only_transaction(instance_id, database_id): if args.command == 'create_database': create_database(args.instance_id, args.database_id) - if args.command == 'insert_data': + elif args.command == 'insert_data': insert_data(args.instance_id, args.database_id) elif args.command == 'query_data': query_data(args.instance_id, args.database_id) From 734f344691e668e04e0aea202a8ae35dbbecdaab Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 14 Feb 2017 11:49:52 -0800 Subject: [PATCH 3/3] Fix test Change-Id: I9d1f01b2ad67b131ff9d0d2b431aa7e33d894d37 --- spanner/cloud-client/quickstart_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spanner/cloud-client/quickstart_test.py b/spanner/cloud-client/quickstart_test.py index d383c2db9740..dafac78b952c 100644 --- a/spanner/cloud-client/quickstart_test.py +++ b/spanner/cloud-client/quickstart_test.py @@ -41,7 +41,7 @@ def new_instance(self, unused_instance_name): def example_database(cloud_config): spanner_client = spanner.Client() instance = spanner_client.instance(cloud_config.spanner_instance) - database = instance.database('my-database-name') + database = instance.database('my-database-id') if not database.exists(): database.create()