Funkcje HTTP w Cloud Functions w Pythonie

1. Wprowadzenie

b158ce75c3cccd6d.png

Python to popularny język programowania typu open source, którego używają m.in. badacze danych, deweloperzy aplikacji internetowych i administratorzy systemów.

Cloud Functions to bezserwerowa platforma obliczeniowa oparta na zdarzeniach. Cloud Functions pozwala pisać kod bez konieczności martwienia się o udostępnianie zasobów czy skalowanie w celu spełnienia zmieniających się wymagań.

Istnieją 2 typy funkcji w Cloud Functions:

  • Funkcje HTTP odpowiadają na żądania HTTP. W tym ćwiczeniu utworzysz kilka takich funkcji.
  • Funkcje działające w tle są aktywowane przez zdarzenia, takie jak opublikowanie wiadomości w Cloud Pub/Sub lub przesłanie pliku do Cloud Storage. Nie omawiamy tego w tym module, ale więcej informacji znajdziesz w dokumentacji.

efb3268e3b74ed4f.png

To ćwiczenie praktyczne przeprowadzi Cię przez proces tworzenia własnych funkcji w Cloud Functions w Pythonie.

Co utworzysz

W tym ćwiczeniu opublikujesz funkcję w Cloud Functions, która po wywołaniu przez HTTP wyświetla logo „Python Powered”:

a7aaf656b78050fd.png

Czego się nauczysz

  • jak napisać funkcję HTTP w Cloud Functions,
  • jak napisać funkcję HTTP w Cloud Functions, która przyjmuje argumenty;
  • jak przetestować funkcję HTTP w Cloud Functions,
  • jak uruchomić lokalny serwer HTTP w Pythonie, aby wypróbować funkcję.
  • jak napisać funkcję HTTP w Cloud Functions, która zwraca obraz;

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail lub Google Workspace, musisz je utworzyć.

fbef9caa1602edd0.pnga99b7ace416376c4.png5e3ff691252acf41.png

  • Nazwa projektu to wyświetlana nazwa dla uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Zawsze możesz go zaktualizować.
  • Identyfikator projektu jest unikalny we wszystkich projektach Google Cloud i nie można go zmienić po ustawieniu. Konsola Cloud automatycznie generuje unikalny ciąg znaków, który zwykle nie ma znaczenia. W większości modułów codelabs musisz odwołać się do identyfikatora projektu (zwykle identyfikowanego jako PROJECT_ID). Jeśli wygenerowany identyfikator Ci się nie podoba, możesz wygenerować inny losowy identyfikator. Możesz też spróbować użyć własnej nazwy i sprawdzić, czy jest dostępna. Nie można go zmienić po tym kroku i pozostaje on niezmieniony przez cały czas trwania projektu.
  • Dla Twojej informacji: istnieje trzecia wartość, numer projektu, której używają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w konsoli Cloud, aby móc korzystać z zasobów i interfejsów API Google Cloud. Wykonanie tego ćwiczenia w programowaniu nie będzie kosztować dużo, jeśli w ogóle. Aby wyłączyć zasoby i uniknąć naliczania opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub projekt. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Uruchamianie Cloud Shell

Chociaż Google Cloud można obsługiwać zdalnie z laptopa, w tym ćwiczeniu będziesz używać Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.

Aktywowanie Cloud Shell

  1. W konsoli Cloud kliknij Aktywuj Cloud Shell 853e55310c205094.png.

3c1dabeca90e44e5.png

Jeśli uruchamiasz Cloud Shell po raz pierwszy, zobaczysz ekran pośredni z opisem tego środowiska. W takim przypadku kliknij Dalej.

9c92662c6a846a5c.png

Uzyskanie dostępu do środowiska Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.

9f0e51b578fecce5.png

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Większość zadań w tym ćwiczeniu, a być może wszystkie, możesz wykonać w przeglądarce.

Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu.

  1. Aby potwierdzić, że uwierzytelnianie zostało przeprowadzone, uruchom w Cloud Shell to polecenie:
gcloud auth list

Wynik polecenia

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

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Aby potwierdzić, że polecenie gcloud zna Twój projekt, uruchom w Cloud Shell to polecenie:
gcloud config list project

Wynik polecenia

[core]
project = <PROJECT_ID>

Jeśli tak nie jest, możesz go ustawić za pomocą tego polecenia:

gcloud config set project <PROJECT_ID>

Wynik polecenia

Updated property [core/project].

Sprawdzanie, czy interfejsy Cloud Functions i Cloud Build API są włączone

Aby upewnić się, że interfejsy Cloud Functions i Cloud Build API są włączone, uruchom w Cloud Shell to polecenie:

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

Uwaga: Cloud Build zostanie wywołany przez polecenie gcloud functions deploy i automatycznie skompiluje Twój kod do obrazu kontenera.

Pobieranie kodu źródłowego

W terminalu Cloud Shell uruchom te polecenia:

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

Sprawdź zawartość katalogu źródłowego:

ls

Powinny być widoczne te pliki:

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

3. Wprowadzenie do funkcji HTTP w Cloud Functions

Funkcje HTTP w Cloud Functions w Pythonie są pisane jako zwykłe funkcje Pythona. Funkcja musi akceptować pojedynczy argument flask.Request, który zwykle ma nazwę 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")

# ...

Możesz otworzyć plik w preferowanym edytorze wiersza poleceń (nano, vim lub emacs). Możesz też otworzyć go w edytorze Cloud Shell po ustawieniu katalogu źródłowego jako obszaru roboczego:

cloudshell open-workspace .

Wdróżmy tę funkcję jako funkcję HTTP w Cloud Functions za pomocą polecenia gcloud functions deploy:

FUNCTION_NAME="hello_world"

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

Wynik polecenia:

...
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
...

Uwagi dotyczące opcji gcloud functions deploy:

  • --runtime: określa środowisko wykonawcze języka. W przypadku Pythona może to być obecnie python37, python38, python39, python310 lub python312. Zobacz Środowiska wykonawcze.
  • --trigger-http: do funkcji zostanie przypisany punkt końcowy. Żądania HTTP (POST, PUT, GET, DELETE i OPTIONS) wysyłane do punktu końcowego będą wywoływać funkcję.
  • --allow-unauthenticated: funkcja będzie publiczna, co pozwoli na dostęp wszystkim wywołującym bez sprawdzania uwierzytelniania.
  • Więcej informacji znajdziesz w artykule gcloud functions deploy.

Aby przetestować funkcję, możesz kliknąć adres URL httpsTrigger.url wyświetlany w wyniku polecenia powyżej. Możesz też pobrać adres URL automatycznie i wywołać funkcję za pomocą tych poleceń:

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

Powinien pojawić się następujący wynik:

Hello World! 👋

4. Pisanie funkcji HTTP w Cloud Functions, która przyjmuje argumenty

Funkcje są bardziej wszechstronne, gdy akceptują argumenty. Zdefiniujmy nową funkcję hello_name, która obsługuje parametr 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")

# ...

Wdróżmy tę nową funkcję:

FUNCTION_NAME="hello_name"

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

Wynik polecenia:

...
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
...

Aby przetestować funkcję, możesz kliknąć adres URL httpsTrigger.url wyświetlany w wyniku polecenia powyżej. Możesz też pobrać adres URL automatycznie i wywołać funkcję za pomocą tych poleceń:

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

Powinien pojawić się domyślny wynik:

Hello World! 🚀

Otrzymujesz wynik domyślny, ponieważ argument name nie jest ustawiony. Dodaj parametr do adresu URL:

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

Tym razem otrzymasz niestandardową odpowiedź:

Hello YOUR NAME! 🚀

Następnym krokiem jest dodanie testów jednostkowych, aby mieć pewność, że funkcje będą działać zgodnie z oczekiwaniami po zaktualizowaniu kodu źródłowego.

5. Pisanie testów

Funkcje HTTP w Cloud Functions w Pythonie są testowane przy użyciu modułu unittest z biblioteki standardowej. Nie musisz uruchamiać emulatora ani innej symulacji, aby przetestować funkcję – wystarczy zwykły kod Pythona.

Oto jak wygląda test funkcji hello_world i 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. Testy w Pythonie są pisane w taki sam sposób jak inne pliki Pythona. Zaczynają się od zestawu importów, a następnie definiują klasy i funkcje.
  2. Deklaracja testu ma postać class TestHello(TestCase). Musi to być klasa dziedzicząca z unittest.TestCase.
  3. Klasa testowa zawiera metody, z których każda musi zaczynać się od test_, które reprezentują poszczególne elementy testowania.
  4. Każdy element testowania testuje jedną z naszych funkcji, symulując parametr request (czyli zastępując go fałszywym obiektem z określonymi danymi wymaganymi do testu).
  5. Po wywołaniu każdej funkcji test sprawdza odpowiedź HTTP, aby upewnić się, że jest ona zgodna z oczekiwaniami.

main.py zależy od flask, dlatego upewnij się, że w środowisku testowym jest zainstalowana platforma Flask:

pip install flask

Instalacja Flask wyświetli wynik podobny do tego:

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

Uruchom te testy lokalnie:

python -m unittest

Wszystkie 3 testy jednostkowe powinny zostać zaliczone:

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

OK

Następnie utworzysz nową funkcję, która zwraca logo „Python Powered”.

6. Pisanie funkcji HTTP w Cloud Functions „Python Powered”

Sprawmy, aby nowa funkcja była nieco bardziej interesująca, zwracając obraz „Python Powered” w odpowiedzi na każde żądanie:

a7aaf656b78050fd.png

Poniżej znajdziesz kod, który to umożliwia:

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")

Wdróż nową funkcję python_powered:

FUNCTION_NAME="python_powered"

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

Wynik polecenia:

...
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
...

Aby przetestować funkcję, kliknij adres URL httpsTrigger.url wyświetlany w wynikach polecenia powyżej. Jeśli wszystko działa prawidłowo, na nowej karcie przeglądarki zobaczysz logo „Python Powered”.

Następnie utworzysz aplikację, aby móc uruchomić i wypróbować funkcję lokalnie przed wdrożeniem.

7. Lokalne uruchamianie funkcji

Możesz uruchomić funkcję HTTP lokalnie, tworząc aplikację internetową i wywołując funkcję w trasie. Możesz go dodać w tym samym katalogu co funkcja. Plik o nazwie web_app.py zawiera te informacje:

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. Ten plik tworzy aplikację Flask.
  2. Rejestruje ona trasę pod podstawowym adresem URL, która jest obsługiwana przez funkcję o nazwie index().
  3. Funkcja index() wywołuje następnie funkcję python_powered, przekazując jej bieżące żądanie.

Upewnij się, że w środowisku programistycznym jest zainstalowana platforma Flask:

pip install flask

Instalacja Flask wyświetli wynik podobny do tego:

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

Aby uruchomić tę aplikację lokalnie, uruchom to polecenie:

python web_app.py

Teraz użyj podglądu Cloud Shell w przeglądarce, aby przetestować aplikację internetową w przeglądarce. W Cloud Shell kliknij przycisk „Podgląd w przeglądarce” i wybierz „Podejrzyj na porcie 8080”:

6c9ff9e5c692c58e.gif

Cloud Shell otworzy adres URL umożliwiający podgląd w nowym oknie przeglądarki, korzystając z własnej usługi pośredniczącej. Podgląd w przeglądarce ogranicza dostęp przez HTTPS tylko do Twojego konta użytkownika. Jeśli wszystko działa prawidłowo, powinno pojawić się logo „Python Powered”.

8e5c3ead11cfd103.png

8. Gratulacje!

b158ce75c3cccd6d.png

Udało Ci się wdrożyć funkcje HTTP w Cloud Functions, używając funkcji idiomatycznych, które obsługują żądania sieciowe za pomocą platformy Flask.

Ceny Cloud Functions zależą od tego, jak często funkcja jest wywoływana. W przypadku funkcji, które nie są często uruchamiane, dostępny jest poziom bezpłatny. Po zakończeniu testowania funkcji w Cloud Functions możesz je usunąć za pomocą gcloud:

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

Funkcje możesz też usunąć w konsoli Google Cloud.

Mamy nadzieję, że korzystanie z Cloud Functions w Pythonie będzie dla Ciebie przyjemne.