HTTP-функции Cloud Functions на Python

1. Введение

b158ce75c3cccd6d.png

Python – это популярный язык программирования с открытым исходным кодом, который используется специалистами по обработке данных, разработчиками веб-приложений, системными администраторами и другими специалистами.

Cloud Functions – это бессерверная вычислительная платформа, управляемая событиями. Cloud Functions позволяет писать код, не беспокоясь о выделении ресурсов или масштабировании для удовлетворения меняющихся требований.

Существует два типа функций Cloud Functions:

  • Функции HTTP отвечают на HTTP-запросы. В этой практической работе вы создадите несколько таких виджетов.
  • Фоновые функции запускаются событиями, например публикацией сообщения в Cloud Pub/Sub или загрузкой файла в Cloud Storage. В этой лабораторной работе мы не будем рассматривать этот вопрос, но вы можете ознакомиться с документацией.

efb3268e3b74ed4f.png

В этой практической работе вы научитесь создавать собственные функции Cloud Functions на Python.

Что вы создадите

В этой практической работе вы опубликуете функцию Cloud Functions, которая при вызове через HTTP отображает логотип Python Powered.

a7aaf656b78050fd.png

Что вы узнаете

  • Как написать HTTP-функцию Cloud Functions.
  • Как написать HTTP-функцию Cloud Functions, которая принимает аргументы.
  • Как протестировать HTTP-функцию Cloud Functions.
  • Как запустить локальный HTTP-сервер Python, чтобы протестировать функцию.
  • Как написать HTTP-функцию Cloud Functions, которая возвращает изображение.

2. Настройка и требования

Самостоятельная настройка среды

  1. Войдите в консоль Google Cloud и создайте новый проект или выберите существующий. Если у вас нет аккаунта Gmail или Google Workspace, создайте его.

fbef9caa1602edd0.pnga99b7ace416376c4.png5e3ff691252acf41.png

  • Название проекта – это отображаемое название для участников проекта. Это строка символов, которая не используется API Google. Вы можете изменить его в любое время.
  • Идентификатор проекта уникален для всех облачных проектов Google Cloud и не может быть изменен после создания. Консоль Cloud автоматически генерирует уникальную строку. Обычно ее значение не имеет значения. В большинстве практических работ вам нужно будет ссылаться на идентификатор проекта (обычно обозначаемый как PROJECT_ID). Если вам не нравится сгенерированный идентификатор, вы можете сгенерировать другой случайный. Вы также можете попробовать ввести свой вариант и проверить, доступен ли он. После этого шага его нельзя будет изменить, и оно останется на протяжении всего проекта.
  • Для некоторых API используется третье значение – номер проекта. Подробную информацию обо всех трех значениях можно найти в документации.
  1. Затем вам нужно будет включить платежный аккаунт в Cloud Console, чтобы использовать ресурсы и API Cloud. Выполнение этой практической работы не потребует больших затрат, если они вообще будут. Чтобы избежать дополнительных расходов, вы можете удалить созданные ресурсы или проект. Новые пользователи Google Cloud могут получить 300 долларов США на бесплатный пробный период.

Запустить Cloud Shell

Хотя Google Cloud можно управлять удаленно с ноутбука, в этой практической работе вы будете использовать Cloud Shell – среду командной строки, работающую в облаке.

Активировать Cloud Shell

  1. В Cloud Console нажмите Activate Cloud Shell (Активировать Cloud Shell) 853e55310c205094.png.

3c1dabeca90e44e5.png

Если вы запускаете Cloud Shell впервые, откроется экран с описанием этого инструмента. Нажмите Continue (Продолжить).

9c92662c6a846a5c.png

Инициализация и подключение к Cloud Shell займут всего несколько минут.

9f0e51b578fecce5.png

На этой виртуальной машине установлены все необходимые инструменты разработки. Она предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Большую часть работы, если не всю, в этой практической работе можно выполнить в браузере.

После подключения к Cloud Shell вы увидите, что аутентификация выполнена и в качестве проекта выбран ваш идентификатор проекта.

  1. Чтобы убедиться, что аутентификация выполнена, запустите в Cloud Shell следующую команду:
gcloud auth list

Результат выполнения команды

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Чтобы убедиться, что команда gcloud распознает ваш проект, выполните в Cloud Shell следующую команду:
gcloud config list project

Результат выполнения команды

[core]
project = <PROJECT_ID>

Если нет, задайте его с помощью следующей команды:

gcloud config set project <PROJECT_ID>

Результат выполнения команды

Updated property [core/project].

Убедитесь, что Cloud Functions API и Cloud Build API включены

Чтобы убедиться, что Cloud Functions и Cloud Build API включены, выполните в Cloud Shell следующую команду:

gcloud services enable \
  cloudfunctions.googleapis.com \
  cloudbuild.googleapis.com

Примечание. Cloud Build будет вызван командой gcloud functions deploy и автоматически соберет ваш код в образ контейнера.

Скачайте исходный код

В терминале Cloud Shell выполните следующие команды:

REPO_NAME="codelabs"
REPO_URL="https://github.com/GoogleCloudPlatform/$REPO_NAME"
SOURCE_DIR="cloud-functions-python-http"

git clone --no-checkout --filter=blob:none --depth=1 $REPO_URL
cd $REPO_NAME
git sparse-checkout set $SOURCE_DIR
git checkout
cd $SOURCE_DIR

Посмотрите, что внутри каталога с исходными файлами:

ls

У вас должны быть следующие файлы:

main.py  python-powered.png  test_main.py  web_app.py

3. Общие сведения об HTTP-функциях Cloud Functions

HTTP-функции Cloud Functions на Python пишутся как обычные функции Python. Функция должна принимать один аргумент flask.Request, который обычно называется request.

main.py

import flask


def hello_world(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - "Hello World! 👋"
    """
    response = "Hello World! 👋"

    return flask.Response(response, mimetype="text/plain")

# ...

Вы можете открыть файл в любом редакторе командной строки, например nano, vim или emacs. Вы также можете открыть его в редакторе Cloud Shell, указав каталог с исходными файлами в качестве рабочей области:

cloudshell open-workspace .

Давайте развернем эту функцию как HTTP-функцию Cloud Functions с помощью команды gcloud functions deploy:

FUNCTION_NAME="hello_world"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

Результат выполнения команды:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

Примечания о параметрах gcloud functions deploy:

  • --runtime – среда выполнения языка. Для Python это может быть python37, python38, python39, python310 или python312. Подробнее о средах выполнения…
  • --trigger-http – функции будет назначена конечная точка. HTTP-запросы (POST, PUT, GET, DELETE и OPTIONS) к конечной точке будут запускать выполнение функции.
  • --allow-unauthenticated: функция будет общедоступной, то есть ее смогут вызывать все пользователи без проверки аутентификации.
  • Подробнее о команде gcloud functions deploy…

Чтобы протестировать функцию, нажмите на URL httpsTrigger.url, который показан в результате выполнения команды выше. Вы также можете программно получить URL и вызвать функцию с помощью следующих команд:

URL=$(gcloud functions describe $FUNCTION_NAME --format "value(httpsTrigger.url)")
curl -w "\n" $URL

Должен получиться следующий результат:

Hello World! 👋

4. Написание HTTP-функции Cloud Functions, которая принимает аргументы

Функции становятся более универсальными, когда они принимают аргументы. Давайте определим новую функцию hello_name, которая поддерживает параметр name:

main.py

# ...

def hello_name(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - "Hello {NAME}! 🚀" if "name=NAME" is defined in the GET request
    - "Hello World! 🚀" otherwise
    """
    name = request.args.get("name", "World")
    response = f"Hello {name}! 🚀"

    return flask.Response(response, mimetype="text/plain")

# ...

Давайте развернем эту новую функцию:

FUNCTION_NAME="hello_name"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

Результат выполнения команды:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

Чтобы протестировать функцию, нажмите на URL httpsTrigger.url, который показан в результате выполнения команды выше. Вы также можете программно получить URL и вызвать функцию с помощью следующих команд:

URL=$(gcloud functions describe $FUNCTION_NAME --format "value(httpsTrigger.url)")
curl -w "\n" $URL

Вы должны получить результат по умолчанию:

Hello World! 🚀

Вы получаете результат по умолчанию, потому что аргумент name не задан. Добавьте параметр в URL:

curl -w "\n" $URL?name=YOUR%20NAME

На этот раз вы получите собственный ответ:

Hello YOUR NAME! 🚀

Следующий шаг – добавить модульные тесты, чтобы убедиться, что функции продолжают работать должным образом при обновлении исходного кода.

5. Как писать тесты

HTTP-функции Cloud Functions на Python тестируются с помощью модуля unittest из стандартной библиотеки. Для тестирования функции не нужно запускать эмулятор или другую симуляцию – достаточно обычного кода Python.

Вот как выглядит тест для функций hello_world и hello_name:

test_main.py

import unittest
import unittest.mock

import main


class TestHello(unittest.TestCase):
    def test_hello_world(self):
        request = unittest.mock.Mock()

        response = main.hello_world(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == "Hello World! 👋"

    def test_hello_name_no_name(self):
        request = unittest.mock.Mock(args={})

        response = main.hello_name(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == "Hello World! 🚀"

    def test_hello_name_with_name(self):
        name = "FirstName LastName"
        request = unittest.mock.Mock(args={"name": name})

        response = main.hello_name(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == f"Hello {name}! 🚀"
  1. Тесты Python пишутся так же, как и другие файлы Python. Они начинаются с набора импортов, а затем определяют классы и функции.
  2. Декларация теста имеет вид class TestHello(TestCase). Это должен быть класс, унаследованный от unittest.TestCase.
  3. В тестовом классе есть методы, каждый из которых должен начинаться с test_ и представлять отдельный тестовый сценарий.
  4. Каждый тестовый сценарий проверяет одну из наших функций, имитируя параметр request (т.е. заменяя его поддельным объектом с конкретными данными, необходимыми для теста).
  5. После вызова каждой функции тест проверяет HTTP-ответ, чтобы убедиться, что он соответствует ожидаемому.

Поскольку main.py зависит от flask, убедитесь, что фреймворк Flask установлен в вашей тестовой среде:

pip install flask

После установки Flask вы увидите примерно следующее:

Collecting flask
...
Successfully installed ... flask-3.0.2 ...

Выполните следующие тесты локально:

python -m unittest

Три модульных теста должны пройти успешно:

...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

Теперь создайте новую функцию, которая возвращает логотип Python Powered.

6. Написание HTTP-функции Cloud Functions на Python

Давайте сделаем новую функцию немного более интересной, возвращая изображение "Python Powered" для каждого запроса:

a7aaf656b78050fd.png

Ниже приведен код, который позволяет это сделать.

main.py

# ...

def python_powered(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - The official "Python Powered" logo
    """
    return flask.send_file("python-powered.png")

Разверните новую функцию python_powered:

FUNCTION_NAME="python_powered"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

Результат выполнения команды:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

Чтобы протестировать функцию, нажмите на URL httpsTrigger.url, показанный в результате выполнения команды выше. Если все работает правильно, в новой вкладке браузера появится логотип Python Powered.

Теперь создайте приложение, чтобы запустить и протестировать функцию локально перед развертыванием.

7. Локальный запуск функции

Вы можете запустить HTTP-функцию локально, создав веб-приложение и вызвав функцию в маршруте. Вы можете добавить его в тот же каталог, что и функция. Файл с названием web_app.py содержит следующий контент:

web_app.py

import flask

import main

app = flask.Flask(__name__)


@app.get("/")
def index():
    return main.python_powered(flask.request)


if __name__ == "__main__":
    # Local development only
    # Run "python web_app.py" and open http://localhost:8080
    app.run(host="localhost", port=8080, debug=True)
  1. Этот файл создает приложение Flask.
  2. Он регистрирует маршрут по базовому URL, который обрабатывается функцией index().
  3. Затем функция index() вызывает функцию python_powered, передавая ей текущий запрос.

Убедитесь, что в вашей среде разработки установлен фреймворк Flask:

pip install flask

После установки Flask вы увидите примерно следующее:

Collecting flask
...
Successfully installed ... flask-3.0.2 ...

Чтобы запустить это приложение локально, выполните следующую команду:

python web_app.py

Теперь используйте веб-предпросмотр Cloud Shell, чтобы протестировать веб-приложение в браузере. В Cloud Shell нажмите кнопку Web Preview (Предварительный просмотр) и выберите Preview on port 8080 (Предварительный просмотр на порте 8080).

6c9ff9e5c692c58e.gif

Приложение будет открыто в новой вкладке через прокси-сервер Cloud Shell. Доступ к веб-просмотру по протоколу HTTPS есть только у вашего аккаунта. Если все работает правильно, вы увидите логотип Python Powered.

8e5c3ead11cfd103.png

8. Поздравляем!

b158ce75c3cccd6d.png

Вы развернули HTTP Cloud Functions, используя идиоматические функции, которые обрабатывают веб-запросы с помощью фреймворка Flask.

Стоимость Cloud Functions зависит от того, как часто вызывается функция. Для функций, которые выполняются редко, предусмотрен бесплатный план. После тестирования функций Cloud Functions вы можете удалить их с помощью gcloud:

gcloud functions delete hello_world --quiet
gcloud functions delete hello_name --quiet
gcloud functions delete python_powered --quiet

Вы также можете удалить функции в консоли Google Cloud.

Надеемся, вам понравится использовать Cloud Functions на Python.