diff --git a/functions/README.rst b/functions/README.rst
new file mode 100644
index 00000000000..f81a7a6611a
--- /dev/null
+++ b/functions/README.rst
@@ -0,0 +1,84 @@
+.. This file is automatically generated. Do not edit this file directly.
+
+Google Cloud Functions Python Samples
+===============================================================================
+
+.. image:: https://gstatic.com/cloudssh/images/open-btn.png
+ :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=/README.rst
+
+
+This directory contains samples for Google Cloud Functions. `Cloud Functions`_ is a lightweight, event-based, asynchronous compute solution that allows you to create small, single-purpose functions that respond to Cloud events without the need to manage a server or a runtime environment.
+
+
+
+
+.. _Cloud Functions: https://cloud.google.com/functions/docs/
+
+Setup
+-------------------------------------------------------------------------------
+
+
+Authentication
+++++++++++++++
+
+This sample requires you to have authentication setup. Refer to the
+`Authentication Getting Started Guide`_ for instructions on setting up
+credentials for applications.
+
+.. _Authentication Getting Started Guide:
+ https://cloud.google.com/docs/authentication/getting-started
+
+Install Dependencies
+++++++++++++++++++++
+
+#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions.
+
+ .. _Python Development Environment Setup Guide:
+ https://cloud.google.com/python/setup
+
+#. 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
+-------------------------------------------------------------------------------
+
+- `Hello World`_
+- Concepts_
+- `Logging & Monitoring`_
+- Tips_
+
+
+.. _Hello World: helloworld/
+.. _Concepts: concepts/
+.. _Logging & Monitoring: log/
+.. _Tips: tips/
+
+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/
diff --git a/functions/README.rst.in b/functions/README.rst.in
new file mode 100644
index 00000000000..f86d5bd823b
--- /dev/null
+++ b/functions/README.rst.in
@@ -0,0 +1,18 @@
+# This file is used to generate README.rst
+
+product:
+ name: Google Cloud Functions
+ short_name: GCF
+ url: https://cloud.google.com/functions/docs/
+ description: >
+ Cloud Functions is a lightweight, event-based, asynchronous compute solution that allows you to create small, single-purpose functions that respond to Cloud events without the need to manage a server or a runtime environment.
+
+setup:
+- auth
+- install_deps
+
+samples:
+- name: Hello World
+ file: helloworld/main.py
+
+cloud_client_library: true
diff --git a/functions/concepts/README.md b/functions/concepts/README.md
new file mode 100644
index 00000000000..a889048b17a
--- /dev/null
+++ b/functions/concepts/README.md
@@ -0,0 +1,11 @@
+
+
+# Google Cloud Functions - Concepts sample
+
+See:
+
+* [Cloud Functions Concepts tutorial][tutorial]
+* [Cloud Functions Concepts sample source code][code]
+
+[tutorial]: https://cloud.google.com/functions/docs/concepts/exec
+[code]: main.py
diff --git a/functions/concepts/main.py b/functions/concepts/main.py
new file mode 100644
index 00000000000..43bfd3aabcf
--- /dev/null
+++ b/functions/concepts/main.py
@@ -0,0 +1,117 @@
+# Copyright 2018 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.
+
+import time
+
+
+# [START functions_concepts_stateless]
+# Global variable, modified within the function by using the global keyword.
+count = 0
+
+
+def statelessness(request):
+ """
+ HTTP Cloud Function that counts how many times it is executed
+ within a specific instance.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ global count
+ count += 1
+
+ # Note: the total function invocation count across
+ # all instances may not be equal to this value!
+ return 'Instance execution count: {}'.format(count)
+# [END functions_concepts_stateless]
+
+
+def heavy_computation():
+ return time.time()
+
+
+def light_computation():
+ return time.time()
+
+
+# [START functions_tips_scopes]
+# Global (instance-wide) scope
+# This computation runs at instance cold-start
+instance_var = heavy_computation()
+
+
+def scope_demo(request):
+ """
+ HTTP Cloud Function that declares a variable.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+
+ # Per-function scope
+ # This computation runs every time this function is called
+ function_var = light_computation()
+ return 'Instance: {}; function: {}'.format(instance_var, function_var)
+# [END functions_tips_scopes]
+
+
+# [START functions_concepts_requests]
+def make_request(request):
+ """
+ HTTP Cloud Function that makes another HTTP request.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ import requests
+
+ # The URL to send the request to
+ url = 'http://example.com'
+
+ # Process the request
+ response = requests.get(url)
+ response.raise_for_status()
+ return 'Success!'
+# [END functions_concepts_requests]
+
+
+# [START functions_concepts_after_timeout]
+def timeout(request):
+ print('Function running...')
+ time.sleep(120)
+
+ # May not execute if function's timeout is <2 minutes
+ return 'Done!'
+# [END functions_concepts_after_timeout]
+
+
+# [START functions_concepts_filesystem]
+def list_files(request):
+ import os
+ from os import path
+
+ root = path.dirname(path.abspath(__file__))
+ children = os.listdir(root)
+ files = [c for c in children if path.isfile(path.join(root, c))]
+ return 'Files: {}'.format(files)
+# [END functions_concepts_filesystem]
diff --git a/functions/concepts/main_test.py b/functions/concepts/main_test.py
new file mode 100644
index 00000000000..71e07b50587
--- /dev/null
+++ b/functions/concepts/main_test.py
@@ -0,0 +1,63 @@
+# Copyright 2018 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.
+
+
+import flask
+import pytest
+import requests
+import responses
+
+import main
+
+
+# Create a fake "app" for generating test request contexts.
+@pytest.fixture(scope="module")
+def app():
+ return flask.Flask(__name__)
+
+
+def test_statelessness(app):
+ with app.test_request_context():
+ res = main.statelessness(flask.request)
+ assert res == 'Instance execution count: 1'
+ res = main.statelessness(flask.request)
+ assert res == 'Instance execution count: 2'
+
+
+def test_scope_demo(app):
+ with app.test_request_context():
+ main.scope_demo(flask.request)
+
+
+@responses.activate
+def test_make_request_200(app):
+ responses.add(responses.GET, 'http://example.com',
+ json={'status': 'OK'}, status=200)
+ with app.test_request_context():
+ main.make_request(flask.request)
+
+
+@responses.activate
+def test_make_request_404(app):
+ responses.add(responses.GET, 'http://example.com',
+ json={'error': 'not found'}, status=404)
+ with app.test_request_context():
+ with pytest.raises(requests.exceptions.HTTPError):
+ main.make_request(flask.request)
+
+
+def test_list_files(app):
+ with app.test_request_context():
+ res = main.list_files(flask.request)
+ assert 'main.py' in res
diff --git a/functions/helloworld/README.md b/functions/helloworld/README.md
new file mode 100644
index 00000000000..11d68f93063
--- /dev/null
+++ b/functions/helloworld/README.md
@@ -0,0 +1,11 @@
+
+
+# Google Cloud Functions - Hello World sample
+
+See:
+
+* [Cloud Functions Hello World tutorial][tutorial]
+* [Cloud Functions Hello World sample source code][code]
+
+[tutorial]: https://cloud.google.com/functions/docs/quickstart
+[code]: main.py
diff --git a/functions/helloworld/main.py b/functions/helloworld/main.py
new file mode 100644
index 00000000000..45b779f16f3
--- /dev/null
+++ b/functions/helloworld/main.py
@@ -0,0 +1,171 @@
+# Copyright 2018 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.
+
+# [START functions_helloworld_error]
+import logging
+# [END functions_helloworld_error]
+import sys
+
+
+# [START functions_tips_terminate]
+# [START functions_helloworld_get]
+def hello_get(request):
+ """HTTP Cloud Function.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ return 'Hello, World!'
+# [END functions_helloworld_get]
+
+
+# [START functions_helloworld_background]
+def hello_background(data, context):
+ """Background Cloud Function.
+ Args:
+ event (dict): The dictionary with data specific to the given event.
+ context (google.cloud.functions.Context): The Cloud Functions event
+ context.
+ """
+ if data and 'name' in data:
+ name = data['name']
+ else:
+ name = 'World'
+ return 'Hello, {}!'.format(name)
+# [END functions_helloworld_background]
+# [END functions_tips_terminate]
+
+
+# [START functions_helloworld_http]
+def hello_http(request):
+ """HTTP Cloud Function.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ request_json = request.get_json()
+ if request_json and 'message' in request_json:
+ name = request_json['message']
+ else:
+ name = 'World'
+ return 'Hello, {}!'.format(name)
+# [END functions_helloworld_http]
+
+
+# [START functions_helloworld_pubsub]
+def hello_pubsub(data, context):
+ """Background Cloud Function to be triggered by Pub/Sub.
+ Args:
+ event (dict): The dictionary with data specific to this type of event.
+ context (google.cloud.functions.Context): The Cloud Functions event
+ context.
+ """
+ import base64
+
+ if 'data' in data:
+ name = base64.b64decode(data['data']).decode('utf-8')
+ else:
+ name = 'World'
+ print('Hello, {}!'.format(name))
+# [END functions_helloworld_pubsub]
+
+
+# [START functions_helloworld_storage]
+def hello_gcs(event, context):
+ """Background Cloud Function to be triggered by Cloud Storage.
+ Args:
+ event (dict): The dictionary with data specific to this type of event.
+ context (google.cloud.functions.Context): The Cloud Functions
+ event context.
+ """
+ print("File: {}.".format(event['objectId']))
+# [END functions_helloworld_storage]
+
+
+# [START functions_http_content]
+def hello_content(request):
+ """ Responds to an HTTP request using data from the request body parsed
+ according to the "content-type" header.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ content_type = request.headers['content-type']
+ if content_type == 'application/json':
+ name = request.json.get('name')
+ elif content_type == 'application/octet-stream':
+ name = request.data
+ elif content_type == 'text/plain':
+ name = request.data
+ elif content_type == 'application/x-www-form-urlencoded':
+ name = request.form.get('name')
+ else:
+ raise ValueError("Unknown content type: {}".format(content_type))
+ return 'Hello, {}!'.format(name)
+# [END functions_http_content]
+
+
+# [START functions_http_methods]
+def hello_method(request):
+ """ Responds to a GET request with "Hello world!". Forbids a PUT request.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ from flask import abort
+
+ if request.method == 'GET':
+ return 'Hello, World!'
+ elif request.method == 'PUT':
+ return abort(403)
+ else:
+ return abort(405)
+# [END functions_http_methods]
+
+
+def hello_error_1(request):
+ # [START functions_helloworld_error]
+ # This WILL be reported to Stackdriver Error Reporting,
+ # and WILL terminate the function
+ raise RuntimeError('I failed you')
+
+ # [END functions_helloworld_error]
+
+
+def hello_error_2(request):
+ # [START functions_helloworld_error]
+ # WILL NOT be reported to Stackdriver Error Reporting, but will show up
+ # in logs
+ print(RuntimeError('I failed you (print to stdout)'))
+ logging.warn(RuntimeError('I failed you (logging.warn)'))
+ logging.error(RuntimeError('I failed you (logging.error)'))
+ sys.stderr.write('I failed you (sys.stderr.write)\n')
+
+ # WILL NOT be reported to Stackdriver Error Reporting, but will show up
+ # in request logs (as a 500 response)
+ from flask import abort
+ return abort(500)
+ # [END functions_helloworld_error]
diff --git a/functions/helloworld/main_test.py b/functions/helloworld/main_test.py
new file mode 100644
index 00000000000..fcd00eab99a
--- /dev/null
+++ b/functions/helloworld/main_test.py
@@ -0,0 +1,62 @@
+# Copyright 2018 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.
+
+import flask
+import pytest
+
+import main
+
+
+# Create a fake "app" for generating test request contexts.
+@pytest.fixture(scope="module")
+def app():
+ return flask.Flask(__name__)
+
+
+def test_hello_get(app):
+ with app.test_request_context():
+ res = main.hello_get(flask.request)
+ assert 'Hello, World!' in res
+
+
+def test_hello_http_no_args(app):
+ with app.test_request_context():
+ res = main.hello_http(flask.request)
+ assert 'Hello, World!' in res
+
+
+def test_hello_http_args(app):
+ with app.test_request_context(json={'message': 'test'}):
+ res = main.hello_http(flask.request)
+ assert 'Hello, test!' in res
+
+
+def test_hello_content_json(app):
+ with app.test_request_context(json={'name': 'test'}):
+ res = main.hello_content(flask.request)
+ assert 'Hello, test!' in res
+
+
+def test_hello_content_urlencoded(app):
+ with app.test_request_context(
+ data={'name': 'test'},
+ content_type='application/x-www-form-urlencoded'):
+ res = main.hello_content(flask.request)
+ assert 'Hello, test!' in res
+
+
+def test_hello_method(app):
+ with app.test_request_context(method='GET'):
+ res = main.hello_method(flask.request)
+ assert 'Hello, World!' in res
diff --git a/functions/helloworld/sample_http_test.py b/functions/helloworld/sample_http_test.py
new file mode 100644
index 00000000000..316422ca87f
--- /dev/null
+++ b/functions/helloworld/sample_http_test.py
@@ -0,0 +1,34 @@
+# Copyright 2018 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.
+
+# [START functions_http_unit_test]
+from unittest.mock import Mock
+
+import main
+
+
+def test_print_name():
+ name = 'test'
+ req = Mock(get_json=Mock(return_value={'message': name}))
+
+ # Call tested function
+ assert main.hello_http(req) == 'Hello, {}!'.format(name)
+
+
+def test_print_hello_world():
+ req = Mock(get_json=Mock(return_value={}))
+
+ # Call tested function
+ assert main.hello_http(req) == 'Hello, World!'
+# [END functions_http_unit_test]
diff --git a/functions/helloworld/sample_http_test_system.py b/functions/helloworld/sample_http_test_system.py
new file mode 100644
index 00000000000..2f629881c31
--- /dev/null
+++ b/functions/helloworld/sample_http_test_system.py
@@ -0,0 +1,40 @@
+# Copyright 2018 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.
+
+# [START functions_http_system_test]
+import os
+import uuid
+
+import requests
+
+
+def test_no_args():
+ BASE_URL = os.getenv('BASE_URL')
+ assert BASE_URL is not None
+
+ res = requests.get('{}/hello_http'.format(BASE_URL))
+ assert res.text == 'Hello, World!'
+
+
+def test_args():
+ BASE_URL = os.getenv('BASE_URL')
+ assert BASE_URL is not None
+
+ name = str(uuid.uuid4())
+ res = requests.post(
+ '{}/hello_http'.format(BASE_URL),
+ json={'message': name}
+ )
+ assert res.text == 'Hello, {}!'.format(name)
+# [END functions_http_system_test]
diff --git a/functions/helloworld/sample_pubsub_test.py b/functions/helloworld/sample_pubsub_test.py
new file mode 100644
index 00000000000..26ef363feaf
--- /dev/null
+++ b/functions/helloworld/sample_pubsub_test.py
@@ -0,0 +1,38 @@
+# Copyright 2018 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.
+
+# [START functions_pubsub_unit_test]
+import base64
+
+import main
+
+
+def test_print_hello_world(capsys):
+ data = {}
+
+ # Call tested function
+ main.hello_pubsub(data, None)
+ out, err = capsys.readouterr()
+ assert out == 'Hello, World!\n'
+
+
+def test_print_name(capsys):
+ name = 'test'
+ data = {'data': base64.b64encode(name.encode())}
+
+ # Call tested function
+ main.hello_pubsub(data, None)
+ out, err = capsys.readouterr()
+ assert out == 'Hello, {}!\n'.format(name)
+# [END functions_pubsub_unit_test]
diff --git a/functions/helloworld/sample_storage_test.py b/functions/helloworld/sample_storage_test.py
new file mode 100644
index 00000000000..aac1769b26b
--- /dev/null
+++ b/functions/helloworld/sample_storage_test.py
@@ -0,0 +1,27 @@
+# Copyright 2018 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.
+
+# [START functions_storage_unit_test]
+import main
+
+
+def test_print(capsys):
+ name = 'test'
+ data = {'objectId': name}
+
+ # Call tested function
+ main.hello_gcs(data, None)
+ out, err = capsys.readouterr()
+ assert out == 'File: {}.\n'.format(name)
+# [END functions_storage_unit_test]
diff --git a/functions/log/README.md b/functions/log/README.md
new file mode 100644
index 00000000000..21bdd9548ec
--- /dev/null
+++ b/functions/log/README.md
@@ -0,0 +1,11 @@
+
+
+# Google Cloud Functions - Logging and Monitoring sample
+
+* [Writing and Viewing Logs from Cloud Functions documentation][docs]
+* [Viewing Cloud Functions monitored metrics documentation][docs2]
+* [Background functions sample source code][code]
+
+[docs]: https://cloud.google.com/functions/docs/monitoring/logging
+[docs2]: https://cloud.google.com/functions/docs/monitoring/metrics
+[code]: main.py
diff --git a/functions/log/main.py b/functions/log/main.py
new file mode 100644
index 00000000000..4353ec0ede4
--- /dev/null
+++ b/functions/log/main.py
@@ -0,0 +1,28 @@
+# Copyright 2018 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.
+
+# [START functions_log_helloworld]
+import logging
+
+
+def hello_world(event, context):
+ """Background Cloud Function.
+ Args:
+ event (dict): The dictionary with data specific to the given event.
+ context (google.cloud.functions.Context): The Cloud Functions event
+ context.
+ """
+ print('Hello, stdout!')
+ logging.warn('Hello, logging handler!')
+# [END functions_log_helloworld]
diff --git a/functions/log/main_test.py b/functions/log/main_test.py
new file mode 100644
index 00000000000..5450a75434d
--- /dev/null
+++ b/functions/log/main_test.py
@@ -0,0 +1,23 @@
+# Copyright 2018 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.
+
+
+import main
+
+
+def test_hello_world(capsys):
+ main.hello_world(None, None)
+
+ out, _ = capsys.readouterr()
+ assert "Hello, stdout!" in out
diff --git a/functions/tips/README.md b/functions/tips/README.md
new file mode 100644
index 00000000000..c6ff906dab9
--- /dev/null
+++ b/functions/tips/README.md
@@ -0,0 +1,11 @@
+
+
+# Google Cloud Functions - Tips sample
+
+See:
+
+* [Cloud Functions Tips tutorial][tutorial]
+* [Cloud Functions Tips sample source code][code]
+
+[tutorial]: https://cloud.google.com/functions/docs/bestpractices/tips
+[code]: main.py
diff --git a/functions/tips/main.py b/functions/tips/main.py
new file mode 100644
index 00000000000..2ee083f42a9
--- /dev/null
+++ b/functions/tips/main.py
@@ -0,0 +1,126 @@
+# Copyright 2018 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.
+
+# [START functions_tips_infinite_retries]
+from datetime import datetime
+
+# The 'python-dateutil' package must be included in requirements.txt.
+from dateutil import parser
+
+# [END functions_tips_infinite_retries]
+# [START functions_tips_connection_pooling]
+import requests
+
+# [END functions_tips_connection_pooling]
+
+
+def file_wide_computation():
+ return sum(range(10))
+
+
+def function_specific_computation():
+ return sum(range(10))
+
+
+# [START functions_tips_lazy_globals]
+# Always initialized (at cold-start)
+non_lazy_global = file_wide_computation()
+
+# Declared at cold-start, but only initialized if/when the function executes
+lazy_global = None
+
+
+def lazy_globals(request):
+ """
+ HTTP Cloud Function that uses lazily-initialized globals.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+ global lazy_global, non_lazy_global
+
+ # This value is initialized only if (and when) the function is called
+ if not lazy_global:
+ lazy_global = function_specific_computation()
+
+ return 'Lazy: {}, non-lazy: {}.'.format(lazy_global, non_lazy_global)
+# [END functions_tips_lazy_globals]
+
+
+# [START functions_tips_connection_pooling]
+# Create a global HTTP session (which provides connection pooling)
+session = requests.Session()
+
+
+def connection_pooling(request):
+ """
+ HTTP Cloud Function that uses a connection pool to make HTTP requests.
+ Args:
+ request (flask.Request): The request object.
+ Returns:
+ The response text, or any set of values that can be turned into a
+ Response object using `make_response`
+ .
+ """
+
+ # The URL to send the request to
+ url = 'http://example.com'
+
+ # Process the request
+ response = session.get(url)
+ response.raise_for_status()
+ return 'Success!'
+# [END functions_tips_connection_pooling]
+
+
+# [START functions_tips_infinite_retries]
+def avoid_infinite_retries(event, context):
+ timestamp = event.timestamp
+
+ event_time = parser.parse(timestamp)
+ event_age = (datetime.now() - event_time).total_seconds() * 1000
+
+ # Ignore events that are too old
+ if event_age > 10000:
+ print('Dropped {} (age {}ms)'.format(context.event_id, event_age))
+ return 'Timeout'
+
+ # Do what the function is supposed to do
+ print('Processed {} (age {}ms)'.format(context.event_id, event_age))
+ return
+# [END functions_tips_infinite_retries]
+
+
+# [START functions_tips_retry]
+def retry_or_not(event, context):
+ from google import cloud
+ error_client = cloud.error_reporting.Client()
+
+ if event.data.get('retry'):
+ try_again = True
+ else:
+ try_again = False
+
+ try:
+ raise Exception('I failed you')
+ except Exception as e:
+ error_client.report_exception()
+ if try_again:
+ raise e # Raise the exception and try again
+ else:
+ return # Swallow the exception and don't retry
+# [END functions_tips_retry]
diff --git a/functions/tips/main_test.py b/functions/tips/main_test.py
new file mode 100644
index 00000000000..110fc6459e4
--- /dev/null
+++ b/functions/tips/main_test.py
@@ -0,0 +1,90 @@
+# Copyright 2018 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.
+
+import datetime
+from unittest.mock import MagicMock, Mock, patch
+
+import flask
+import pytest
+import requests
+import responses
+
+import main
+
+
+# Create a fake "app" for generating test request contexts.
+@pytest.fixture(scope="module")
+def app():
+ return flask.Flask(__name__)
+
+
+def test_lazy_globals(app):
+ with app.test_request_context():
+ main.lazy_globals(flask.request)
+
+
+@responses.activate
+def test_connection_pooling_200(app):
+ responses.add(responses.GET, 'http://example.com',
+ json={'status': 'OK'}, status=200)
+ with app.test_request_context():
+ main.connection_pooling(flask.request)
+
+
+@responses.activate
+def test_connection_pooling_404(app):
+ responses.add(responses.GET, 'http://example.com',
+ json={'error': 'not found'}, status=404)
+ with app.test_request_context():
+ with pytest.raises(requests.exceptions.HTTPError):
+ main.connection_pooling(flask.request)
+
+
+def test_avoid_infinite_retries(capsys):
+ now = datetime.datetime.now()
+
+ with patch('main.datetime', wraps=datetime.datetime) as datetime_mock:
+ datetime_mock.now = Mock(return_value=now)
+ old_event = Mock(
+ timestamp=(now - datetime.timedelta(seconds=15)).isoformat())
+ young_event = Mock(
+ timestamp=(now - datetime.timedelta(seconds=5)).isoformat())
+ context = Mock(event_id='fake_event_id')
+
+ main.avoid_infinite_retries(old_event, context)
+ out, _ = capsys.readouterr()
+ assert 'Dropped {} (age 15000.0ms)'.format(context.event_id) in out
+
+ main.avoid_infinite_retries(young_event, context)
+ out, _ = capsys.readouterr()
+ assert 'Processed {} (age 5000.0ms)'.format(context.event_id) in out
+
+
+def test_retry_or_not():
+ with patch('google.cloud') as cloud_mock:
+
+ error_client = MagicMock()
+
+ cloud_mock.error_reporting = MagicMock(
+ Client=MagicMock(return_value=error_client))
+
+ event = Mock(data={})
+ main.retry_or_not(event, None)
+ assert error_client.report_exception.call_count == 1
+
+ event.data = {'retry': True}
+ with pytest.raises(Exception):
+ main.retry_or_not(event, None)
+
+ assert error_client.report_exception.call_count == 2
diff --git a/functions/tips/requirements.txt b/functions/tips/requirements.txt
new file mode 100644
index 00000000000..3aeddb98016
--- /dev/null
+++ b/functions/tips/requirements.txt
@@ -0,0 +1,2 @@
+google-cloud-error-reporting==0.30.0
+python-dateutil==2.7.3
\ No newline at end of file