Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 00d75ca

Browse files
author
Jonathan Wayne Parrott
committed
Merge pull request GoogleCloudPlatform#60 from GoogleCloudPlatform/datastore-blog
Adding datastore blog samples and tests.
2 parents b453dc5 + e2d4dfa commit 00d75ca

File tree

11 files changed

+363
-21
lines changed

11 files changed

+363
-21
lines changed

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cache:
66
directories:
77
- $HOME/gcloud/
88
env:
9-
- PATH=$PATH:$HOME/gcloud/google-cloud-sdk/bin GOOGLE_APPLICATION_CREDENTIALS=$TRAVIS_BUILD_DIR/python-docs-samples.json PYTHONPATH=${HOME}/gcloud/google-cloud-sdk/platform/google_appengine TEST_BUCKET_NAME=bigquery-devrel-samples-bucket TEST_PROJECT_ID=bigquery-devrel-samples #Other environment variables on same line
9+
- PATH=$PATH:$HOME/gcloud/google-cloud-sdk/bin GOOGLE_APPLICATION_CREDENTIALS=$TRAVIS_BUILD_DIR/python-docs-samples.json GAE_PYTHONPATH=${HOME}/gcloud/google-cloud-sdk/platform/google_appengine TEST_BUCKET_NAME=bigquery-devrel-samples-bucket TEST_PROJECT_ID=bigquery-devrel-samples #Other environment variables on same line
1010

1111
before_install:
1212
#ENCRYPT YOUR PRIVATE KEY (If you need authentication)
@@ -35,7 +35,10 @@ before_install:
3535
fi
3636

3737
install:
38-
- pip install tox
38+
- pip install tox coveralls
3939

4040
script:
4141
- tox
42+
43+
after_success:
44+
coveralls

appengine/images/tests/test_guestbook.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121

2222

2323
class TestHandlers(DatastoreTestbedCase):
24+
def setUp(self):
25+
super(TestHandlers, self).setUp()
26+
27+
# Workaround for other tests clobbering our Greeting model.
28+
reload(main)
29+
2430
def test_get(self):
2531
# Build a request object passing the URI path to be tested.
2632
# You can also pass headers, query arguments etc.

blog/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Blog Sample Code
2+
3+
This directory contains samples used in the
4+
[Cloud Platform Blog](http://cloud.google.com/blog). Each sample should have a
5+
readme with instructions and a link to its respective blog post.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Introduction to data models in Cloud Datastore
2+
3+
This sample code is used in [this blog post](). It demonstrates two data models
4+
using [Google Cloud Datstore](https://cloud.google.com/datastore).
5+
6+
## Prerequisites
7+
8+
1. Create project with billing enabled on the [Google Developers Console](https://console.developers.google.com)
9+
2. [Enable the Datastore API](https://console.developers.google.com/project/_/apiui/apiview/datastore/overview).
10+
3. Install the [Google Cloud SDK](https://cloud.google.com/sdk) and be sure to run ``gcloud auth``.
11+
12+
13+
## Running the samples
14+
15+
Install any dependencies:
16+
17+
pip install -r requirements.txt
18+
19+
And run the samples:
20+
21+
python blog.py your-project-id
22+
python wiki.py your-project-id
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2015, Google, Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import argparse
15+
import datetime
16+
17+
from gcloud import datastore
18+
19+
20+
def path_to_key(datastore, path):
21+
"""
22+
Translates a file system path to a datastore key. The basename becomes the
23+
key name and the extension becomes the kind.
24+
25+
Examples:
26+
/file.ext -> key(ext, file)
27+
/parent.ext/file.ext -> key(ext, parent, ext, file)
28+
"""
29+
key_parts = []
30+
path_parts = path.strip(u'/').split(u'/')
31+
for n, x in enumerate(path_parts):
32+
name, ext = x.rsplit('.', 1)
33+
key_parts.extend([ext, name])
34+
35+
return datastore.key(*key_parts)
36+
37+
38+
def create_user(ds, username, profile):
39+
key = path_to_key(ds, '{0}.user'.format(username))
40+
entity = datastore.Entity(key)
41+
entity.update(profile)
42+
ds.put(entity)
43+
44+
45+
def create_post(ds, username, post_content):
46+
now = datetime.datetime.utcnow()
47+
key = path_to_key(ds, '{0}.user/{1}.post'.format(username, now))
48+
entity = datastore.Entity(key)
49+
50+
entity.update({
51+
'created': now,
52+
'created_by': username,
53+
'content': post_content
54+
})
55+
56+
ds.put(entity)
57+
58+
59+
def repost(ds, username, original):
60+
now = datetime.datetime.utcnow()
61+
new_key = path_to_key(ds, '{0}.user/{1}.post'.format(username, now))
62+
new = datastore.Entity(new_key)
63+
64+
new.update(original)
65+
66+
ds.put(new)
67+
68+
69+
def list_posts_by_user(ds, username):
70+
user_key = path_to_key(ds, '{0}.user'.format(username))
71+
return ds.query(kind='post', ancestor=user_key).fetch()
72+
73+
74+
def list_all_posts(ds):
75+
return ds.query(kind='post').fetch()
76+
77+
78+
def main(project_id):
79+
ds = datastore.Client(dataset_id=project_id)
80+
81+
print("Creating users...")
82+
create_user(ds, 'tonystark',
83+
{'name': 'Tony Stark', 'location': 'Stark Island'})
84+
create_user(ds, 'peterparker',
85+
{'name': 'Peter Parker', 'location': 'New York City'})
86+
87+
print("Creating posts...")
88+
for n in range(1, 10):
89+
create_post(ds, 'tonystark', "Tony's post #{0}".format(n))
90+
create_post(ds, 'peterparker', "Peter's post #{0}".format(n))
91+
92+
print("Re-posting tony's post as peter...")
93+
94+
tonysposts = list_posts_by_user(ds, 'tonystark')
95+
for post in tonysposts:
96+
original_post = post
97+
break
98+
99+
repost(ds, 'peterparker', original_post)
100+
101+
print('Posts by tonystark:')
102+
for post in list_posts_by_user(ds, 'tonystark'):
103+
print("> {0} on {1}".format(post['content'], post['created']))
104+
105+
print('Posts by peterparker:')
106+
for post in list_posts_by_user(ds, 'peterparker'):
107+
print("> {0} on {1}".format(post['content'], post['created']))
108+
109+
print('Posts by everyone:')
110+
for post in list_all_posts(ds):
111+
print("> {0} on {1}".format(post['content'], post['created']))
112+
113+
print('Cleaning up...')
114+
ds.delete_multi([
115+
path_to_key(ds, 'tonystark.user'),
116+
path_to_key(ds, 'peterparker.user')
117+
])
118+
ds.delete_multi([
119+
x.key for x in list_all_posts(ds)])
120+
121+
122+
if __name__ == "__main__":
123+
parser = argparse.ArgumentParser(
124+
description='Demonstrates wiki data model.')
125+
parser.add_argument('project_id', help='Your cloud project ID.')
126+
127+
args = parser.parse_args()
128+
129+
main(args.project_id)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
gcloud==0.7.0
2+
google-apitools==0.4.8
3+
httplib2==0.9.1
4+
oauth2client==1.4.12
5+
protobuf==3.0.0a1
6+
protorpc==0.10.0
7+
pyasn1==0.1.8
8+
pyasn1-modules==0.0.6
9+
pycrypto==2.6.1
10+
pytz==2015.4
11+
rsa==3.1.4
12+
six==1.9.0
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2015, Google, Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
#
14+
from blog import main
15+
from tests import CloudBaseTest
16+
17+
18+
class BlogTestCase(CloudBaseTest):
19+
"""Simple test case that ensures the blog code doesn't throw any errors."""
20+
21+
def test_main(self):
22+
main(self.constants['projectId'])
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2015, Google, Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import argparse
15+
import datetime
16+
17+
from gcloud import datastore
18+
19+
20+
def path_to_key(datastore, path):
21+
"""
22+
Translates a file system path to a datastore key. The basename becomes the
23+
key name and the extension becomes the kind.
24+
25+
Examples:
26+
/file.ext -> key(ext, file)
27+
/parent.ext/file.ext -> key(ext, parent, ext, file)
28+
"""
29+
key_parts = []
30+
path_parts = path.strip(u'/').split(u'/')
31+
for n, x in enumerate(path_parts):
32+
name, ext = x.rsplit('.', 1)
33+
key_parts.extend([ext, name])
34+
35+
return datastore.key(*key_parts)
36+
37+
38+
def save_page(ds, page, content):
39+
with ds.transaction():
40+
now = datetime.datetime.utcnow()
41+
current_key = path_to_key(ds, '{}.page/current.revision'.format(page))
42+
revision_key = path_to_key(ds, '{}.page/{}.revision'.format(page, now))
43+
44+
if ds.get(revision_key):
45+
raise AssertionError("Revision %s already exists" % revision_key)
46+
47+
current = ds.get(current_key)
48+
49+
if current:
50+
revision = datastore.Entity(key=revision_key)
51+
revision.update(current)
52+
ds.put(revision)
53+
else:
54+
current = datastore.Entity(key=current_key)
55+
56+
current['content'] = content
57+
58+
ds.put(current)
59+
60+
61+
def restore_revision(ds, page, revision):
62+
save_page(ds, page, revision['content'])
63+
64+
65+
def list_pages(ds):
66+
return ds.query(kind='page').fetch()
67+
68+
69+
def list_revisions(ds, page):
70+
page_key = path_to_key(ds, '{}.page'.format(page))
71+
return ds.query(kind='revision', ancestor=page_key).fetch()
72+
73+
74+
def main(project_id):
75+
ds = datastore.Client(dataset_id=project_id)
76+
77+
save_page(ds, 'page1', '1')
78+
save_page(ds, 'page1', '2')
79+
save_page(ds, 'page1', '3')
80+
81+
print('Revisions for page1:')
82+
first_revision = None
83+
for revision in list_revisions(ds, 'page1'):
84+
if not first_revision:
85+
first_revision = revision
86+
print("{}: {}".format(revision.key.name, revision['content']))
87+
88+
print('restoring revision {}:'.format(first_revision.key.name))
89+
restore_revision(ds, 'page1', first_revision)
90+
91+
print('Revisions for page1:')
92+
for revision in list_revisions(ds, 'page1'):
93+
print("{}: {}".format(revision.key.name, revision['content']))
94+
95+
print('Cleaning up')
96+
ds.delete_multi([path_to_key(ds, 'page1.page')])
97+
ds.delete_multi([x.key for x in list_revisions(ds, 'page1')])
98+
99+
100+
if __name__ == "__main__":
101+
parser = argparse.ArgumentParser(
102+
description='Demonstrates wiki data model.')
103+
parser.add_argument('project_id', help='Your cloud project ID.')
104+
105+
args = parser.parse_args()
106+
107+
main(args.project_id)

tests/__init__.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@
2121
import os
2222
import StringIO
2323
import sys
24+
import tempfile
2425
import unittest
2526

26-
from google.appengine.datastore import datastore_stub_util
27-
from google.appengine.ext import testbed
27+
from nose.plugins.skip import SkipTest
28+
29+
try:
30+
APPENGINE_AVAILABLE = True
31+
from google.appengine.datastore import datastore_stub_util
32+
from google.appengine.ext import testbed
33+
except ImportError:
34+
APPENGINE_AVAILABLE = False
2835

2936
BUCKET_NAME_ENV = 'TEST_BUCKET_NAME'
3037
PROJECT_ID_ENV = 'TEST_PROJECT_ID'
@@ -82,12 +89,16 @@ def setUp(self):
8289
self.constants['cloudStorageOutputURI'] % test_bucket_name)
8390

8491
def tearDown(self):
85-
os.environ['SERVER_SOFTWARE'] = self._server_software_org
92+
if self._server_software_org:
93+
os.environ['SERVER_SOFTWARE'] = self._server_software_org
8694

8795

8896
class DatastoreTestbedCase(unittest.TestCase):
8997
"""A base test case for common setup/teardown tasks for test."""
9098
def setUp(self):
99+
if not APPENGINE_AVAILABLE:
100+
raise SkipTest()
101+
91102
"""Setup the datastore and memcache stub."""
92103
# First, create an instance of the Testbed class.
93104
self.testbed = testbed.Testbed()
@@ -99,7 +110,9 @@ def setUp(self):
99110
self.policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(
100111
probability=0)
101112
# Initialize the datastore stub with this policy.
102-
self.testbed.init_datastore_v3_stub(consistency_policy=self.policy)
113+
self.testbed.init_datastore_v3_stub(
114+
datastore_file=tempfile.mkstemp()[1],
115+
consistency_policy=self.policy)
103116
self.testbed.init_memcache_stub()
104117

105118
def tearDown(self):

0 commit comments

Comments
 (0)