Portable Cloud Functions with the Python Functions Framework
Tags: googlecloud, googlecloudrun, cloudfunctions, functionsframework
Published: 2020-01-09 20:10:46

---
title: Portable Cloud Functions with the Python Functions Framework
published: true
description: How to use the recently published Functions Framework for Python to target alternate deployment environments and more.
tags: googlecloud, googlecloudrun, cloudfunctions, functionsframework
---

In this post, I'll show how the open-source [Functions Framework for Python](https://github.com/googlecloudplatform/functions-framework-python) makes Cloud Functions portable across multiple products and ecosystems. I’ll specifically show how to migrate a Python function from Cloud Functions to a service like [Cloud Run](https://cloud.google.com/run/).

I'll also show you how the Functions Framework gives you the ability to test your functions locally as well.

## What are the Functions Frameworks?

The [Function Frameworks](https://github.com/GoogleCloudPlatform/functions-framework) are open source libraries for writing portable functions -- brought to you by the Google Cloud Functions team.

The Functions Framework lets you write lightweight functions that run in many different environments, including:

* Google Cloud Functions
* Cloud Run and Cloud Run on GKE
* Knative-based environments
* Your local development machine
* and elsewhere.

You can think of the Functions Framework for a given language as a wrapper around a single function in that language, which handles everything necessary for your function to receive HTTP requests or Cloud Events such as Pub/Sub.

The [Functions Framework for Python](https://github.com/GoogleCloudPlatform/functions-framework-python) joins previously open-sourced frameworks [for several other popular languages](https://github.com/GoogleCloudPlatform?q=functions-framework).

## Going beyond Cloud Functions

Cloud Functions is a powerful and simple tool for quickly deploying a standalone function that lets you integrate various APIs, products, services, or just respond to events.

However, that simplicity also comes at a cost: the [Cloud Functions Runtime](https://cloud.google.com/functions/docs/concepts/python-runtime) makes some opinionated choices about the environment in which your function runs, such as the runtime’s base image, the patch version of Python, and the underlying system libraries that are installed.

For most developers, this is totally fine, as the choices that have been made for the runtime are appropriate for almost all use cases.

However, occasionally you will want to make a small change, such as install a special platform-level package that you need for a particular task. 

Or, you'll want to continue using your function as-is, but move it to run on Cloud Run to lower costs.

## An example: Portability _without_ the Functions Framework

Let's say we have this minimal Cloud Function:

```python
def hello(request):
    return "Hello world!"
```

This function doesn't do much: it takes an HTTP request, and returns "Hello world!" as a response.

However, if we wanted to migrate it from Cloud Functions to a service like Cloud Run, which hosts applications, not functions, we'd have to do a lot:

* choose a web framework
* configure the web application
* refactor this function as a view in our web app
* add the view as a route

By this point, our "new" function wouldn't look much at all like your original function. If you had chosen Flask as your web framework, it might look something like this:

```python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello world!'
```

That's a lot of extra boilerplate that you don't need.


## An example: Portability _with_ the Functions Framework

With the Functions Framework, none of this refactoring is necessary. You can deploy a function to Cloud Run without changing a single line of code in your `main.py` file.

Instead, you can define a `Dockerfile` as follows:

```bash
# Use the official Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . .

# Install production dependencies.
RUN pip install functions-framework
RUN pip install -r requirements.txt

# Run the web service on container startup.
CMD exec functions-framework --target=hello
```

This `Dockerfile` largely borrows from the `Dockerfile` in the [Python Cloud Run quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy), but with two key differences:

* it installs the [`functions-framework`](https://pypi.org/p/functions-framework/) dependency
* it invokes the Functions Framework in the `CMD` step.

The last steps are building and deploying the container as you would with any Cloud Run application.

[See this entire example on Github here.](https://github.com/GoogleCloudPlatform/functions-framework-python/tree/master/examples/cloud_run)

## A local development experience

Another useful feature of the Functions Framework is the ability to run your Cloud Function locally.

Using the example above, you can run your function on your host machine direction using the Function Framework's handy command-line utility:

```
$ functions-framework --target hello
```

The downside to this is that any dependencies need to be installed globally and that there may be conflicts between what your function requires and what your host machine has available.

Ideally, we would isolate our function by building the container image we defined in our `Dockerfile` and running it locally:

```
$ docker build -t helloworld . && docker run --rm -p 8080:8080 -e PORT=8080 helloworld
```

Both of these will start a local development server at <http://localhost:8080> that you can send HTTP traffic to.

## Bonus: Static Typing

One additional benefit to the Functions Framework is that it makes all of the the necessary types available to statically type your functions.

For example, an HTTP function could be statically typed before:

```python
from typing import Tuple, Union
from flask.wrappers import Request

def my_function(request: Request) -> str:
    ...
```

But now the `Context` type is available for background functions:

```python
from typing import Dict
from google.cloud.functions.context import Context

def my_function(data: Dict, context: Context) -> None:
    ...
```

## Next steps

* Found this project useful? [Give it a star it on Github](https://github.com/GoogleCloudPlatform/functions-framework-python/)
* Found a bug in the Python Functions Framework? [File an issue](https://github.com/GoogleCloudPlatform/functions-framework-python/issues)
* Want similar updates? [Follow me on Twitter]( https://twitter.com/di_codes)

---