1. Introduction
Python is a popular open source programming language used by data scientists, web application developers, systems administrators and more.
Google Cloud Functions is an event-driven serverless compute platform. Cloud Functions allows you to write your code without worrying about provisioning resources or scaling to handle changing requirements.
There are two types of Cloud Functions:
- HTTP functions respond to HTTP requests. You'll build one in this codelab.
- Background functions are triggered by events, like a message being published to Cloud Pub/Sub or a file being uploaded to Cloud Storage. We don't address this in this lab, but you can read more in the documentation.
This codelab will walk you through creating your own Cloud Function in Python.
What you will build
In this codelab, you will publish a Cloud Function that, when invoked via HTTP, will display the "Python Powered" logo.
What you will learn
- How to write an HTTP Cloud Function in Python.
- How to write an HTTP Cloud Function in Python which takes arguments.
- How to test an HTTP Cloud Function in Python.
- How to run a local Python HTTP server to try the function.
2. Setup and Requirements
Self-paced environment setup
- Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.
- The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can update it at any time.
- The Project ID must be unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference the Project ID (it is typically identified as
PROJECT_ID
). If you don't like the generated ID, you may generate another random one. Alternatively, you can try your own and see if it's available. It cannot be changed after this step and will remain for the duration of the project. - For your information, there is a third value, a Project Number which some APIs use. Learn more about all three of these values in the documentation.
- Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab shouldn't cost much, if anything at all. To shut down resources so you don't incur billing beyond this tutorial, you can delete the resources you created or delete the whole project. New users of Google Cloud are eligible for the $300 USD Free Trial program.
Start Cloud Shell
While Google Cloud can be operated remotely from your laptop, in this codelab you will be using Google Cloud Shell, a command line environment running in the Cloud.
Activate Cloud Shell
- From the Cloud Console, click Activate Cloud Shell
.
If you've never started Cloud Shell before, you're presented with an intermediate screen (below the fold) describing what it is. If that's the case, click Continue (and you won't ever see it again). Here's what that one-time screen looks like:
It should only take a few moments to provision and connect to Cloud Shell.
This virtual machine is loaded with all the development tools you need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with simply a browser or your Chromebook.
Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your project ID.
- Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list
Command output
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project
Command output
[core] project = <PROJECT_ID>
If it is not, you can set it with this command:
gcloud config set project <PROJECT_ID>
Command output
Updated property [core/project].
Ensure the Cloud Functions and Cloud Build APIs are enabled
Run the following command from Cloud Shell to make sure the Cloud Functions and Cloud Build APIs are enabled:
gcloud services enable cloudfunctions.googleapis.com cloudbuild.googleapis.com
Note: Cloud Build will be called by the gcloud functions deploy
command and will automatically build your code into a container image.
Download and setup the code
- From the Cloud Shell terminal, use
curl
to download a zip with the code for this codelab:
curl -LO https://github.com/GoogleCloudPlatform/python-docs-samples/archive/main.zip
- Use
unzip
to unpack the code. This unpacks a directory (python-docs-samples-main
), which contains sample Python code for cloud.google.com.
unzip main.zip
- Change to the directory containing the code for this codelab:
cd python-docs-samples-main/codelabs/functions/python_powered
Check out the content of the python_powered
directory:
ls
You should get the following files:
app.py main.py python_powered.jpg test_main.py
3. Introducing HTTP Cloud Functions in Python
HTTP Cloud Functions in Python are written as regular Python functions. The function must accept a single argument, which is usually named request
.
main.py
def hello_world(request):
"""HTTP Cloud Function.
Args:
request (flask.Request): The request object.
<http://flask.pocoo.org/docs/1.0/api/#flask.Request>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
"""
return "Hello World!"
You can open this file in Cloud Shell by opening the editor (click the pencil icon at the top right of Cloud Shell) then using the file tree on the left side of the editor to open the file, python-docs-samples-main/codelabs/functions/python_powered/main.py
.
Let's deploy this function as an HTTP Cloud Function! Make sure you cd python-docs-samples-main/codelabs/functions/python_powered/
, then you can deploy it using the function name and gcloud functions deploy
. This may take a minute or two.
gcloud functions deploy hello_world \ --runtime python310 \ --trigger-http \ --allow-unauthenticated
The --allow-unauthenticated
deploy option enables you to reach the function without authentication.
Command output:
Deploying function (may take a while - up to 2 minutes)...done. availableMemoryMb: 256 entryPoint: hello_world httpsTrigger: url: https://REGION-PROJECT_ID.cloudfunctions.net/hello_world ...
To test the hello_world
function, you can click the httpsTrigger.url
URL displayed in the command output above. You can also programmatically retrieve the URL and call the function with the following commands:
URL=$(gcloud functions describe hello_world --format "value(httpsTrigger.url)") curl -w "\n" $URL
You should get the following result:
Hello World!
4. Writing a Python HTTP Cloud Function which takes arguments
Functions are more useful when they can take arguments. We'll define a new function, hello_name
, which says "Hello World!" by default, but can accept a name
argument which causes it to say "Hello _____!" depending on the value of name
.
main.py
import flask
def hello_name(request):
"""HTTP Cloud Function.
Args:
request (flask.Request): The request object.
<http://flask.pocoo.org/docs/1.0/api/#flask.Request>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
"""
request_args = request.args
if request_args and "name" in request_args:
name = request_args["name"]
else:
name = "World"
return "Hello {}!".format(flask.escape(name))
Again, let's deploy this new function as an HTTP Cloud Function! Make sure you cd python-docs-samples-main/codelabs/functions/python_powered/
, then you can deploy it using the function name and gcloud functions deploy
. Note that this command is slightly different than the one you ran before: we're deploying hello_name
, not hello_world
.
gcloud functions deploy hello_name \ --runtime python310 \ --trigger-http \ --allow-unauthenticated
The --allow-unauthenticated
deploy option enables you to reach the function without authentication.
Command output:
Deploying function (may take a while - up to 2 minutes)...done. availableMemoryMb: 256 entryPoint: hello_name httpsTrigger: url: https://REGION-PROJECT_ID.cloudfunctions.net/hello_name ...
To test the hello_name
function, you can click the httpsTrigger.url
URL displayed in the command output above. You can also programmatically retrieve the URL and call the function with the following commands:
URL=$(gcloud functions describe hello_name --format "value(httpsTrigger.url)") curl -w "\n" $URL
You should get the default result:
Hello World!
You're getting the default result because the name
argument isn't set. To set it, add a URL parameter name=YOURNAME
to the URL. Call it like so, replacing YOURNAME
with your name:
curl -w "\n" $URL?name=YOURNAME
This time, you'll get your custom response:
Hello YOURNAME!
The next step is to add some tests to make sure your functions continue to work.
5. Writing tests
HTTP Cloud Functions in Python are tested using the unittest
module from the standard library. There is no need to run an emulator or other simulation to test your function—just normal Python code.
Here is what a test looks like for the hello_world
and hello_name
functions:
test_main.py
import unittest
import unittest.mock
import main
class TestHello(unittest.TestCase):
def test_hello_world(self):
req = unittest.mock.Mock()
# Call tested function
assert main.hello_world(req) == "Hello World!"
def test_hello_name_no_name(self):
req = unittest.mock.Mock(args={})
# Call tested function
assert main.hello_name(req) == "Hello World!"
def test_hello_name_with_name(self):
name = "test"
req = unittest.mock.Mock(args={"name": name})
# Call tested function
assert main.hello_name(req) == "Hello {}!".format(name)
- Python tests are written the same way as other Python files. They start with a set of imports, then define classes and functions.
- The test declaration is of the form
class TestHello(TestCase)
. It must be a class that inherits fromunittest.TestCase
. - The test class has methods, each of which must start with
test_
, which represent individual test cases. - Each test case tests one of our functions by mocking the
request
parameter (i.e., replacing it with a fake object with the specific data we require for the test). - After invoking each function, the test checks the HTTP response to be sure it was what we were expecting.
As main.py
depends on flask
, make sure the Flask framework is installed in your test environment:
pip install flask
Installing Flask outputs a result similar to the following:
Collecting flask ... Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.2 flask-2.2.2 ...
To run these tests locally, cd
to the directory with the files you're testing then use the python3 -m unittest
command:
python -m unittest ... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
Next, you'll create a new function which returns the ‘Python Powered' logo.
6. Writing the ‘Python Powered' HTTP Cloud Function
Let's make the "Hello World!" function a bit more entertaining by printing the ‘Python Powered' image for every request:
The following listing shows the code to make it happen. You can see this code in Cloud Shell at python-docs-samples-main/codelabs/functions/python_powered/main.py
. After the code block, there are notes about each portion.
main.py
import os
import flask
def python_powered(request):
"""HTTP Cloud Function.
Args:
request (flask.Request): The request object.
<http://flask.pocoo.org/docs/1.0/api/#flask.Request>
Returns:
The response file, a JPG image that says "Python Powered"
"""
return flask.send_from_directory(os.getcwd(), "python_powered.jpg", mimetype="image/jpeg")
- The file starts with an import statement from
flask
of a function we'll use to send files as responses. - Next is the
python_powered
function declaration. - The function calls the
send_from_directory
function with a filename to send (python_powered.jpg
) and a mimetype. - If there was no error when writing the response, the function returns normally.
Deploy this function as you did the "Hello World!" function from before, using gcloud functions deploy
and the name of the function, python_powered
:
gcloud functions deploy python_powered \ --runtime python310 \ --trigger-http \ --allow-unauthenticated
The --allow-unauthenticated
deploy option enables you to reach the function without authentication.
To test the function, visit the function's URL, which again is displayed in the gcloud functions deploy
command output in your browser. If everything is working correctly, you will see the ‘Python Powered' logo in your browser!
Next, you'll create an app so that you can run your function locally and try it in a browser.
7. Running the function locally
You can run an HTTP function locally by creating an HTTP server and calling your function in a route.
You can write an HTTP server for your function in the same directory as your function. Create a file named app.py
with the following contents:
app.py
import flask
import main
app = flask.Flask(__name__)
@app.route("/")
def index():
return main.python_powered(flask.request)
- This file imports
python_powered
from your function file,main.py
, and additional resources fromflask
, a Python web framework. - It creates a Flask application
- It registers a route at the base URL which is handled with a function named
index()
. - The
index()
function then calls ourpython_powered
function, passing it the current request.
Make sure the Flask framework is installed in your development environment:
pip install flask
Installing Flask outputs a result similar to the following:
Collecting flask ... Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.2 flask-2.2.2 ...
To run this application locally, run the following command:
python -m flask run --port 8080
Now use the Cloud Shell Web Preview to test the server in your browser. In Cloud Shell, click the "Web preview" button and select "Preview on port 8080":
Cloud Shell opens the preview URL on its proxy service in a new browser window. The web preview restricts access over HTTPS to your user account only. If everything is working properly, you should see the ‘Python Powered' logo!
8. Cleaning up
Cloud Functions pricing is based on how often your function is invoked, including a free tier for functions that don't run often.
Once you're done testing your Cloud Functions, you can delete them using gcloud
:
gcloud functions delete hello_world --quiet gcloud functions delete hello_name --quiet gcloud functions delete python_powered --quiet
You can also delete the functions from the Cloud Console UI.
We hope you enjoy using Cloud Functions in Python!