Funkcje HTTP w Cloud Functions w Pythonie

1. Wprowadzenie

b158ce75c3cccd6d.png

Python to popularny język programowania typu open source wykorzystywany przez badaczy danych, programistów aplikacji internetowych i administratorów systemów.

Cloud Functions to bezserwerowa platforma obliczeniowa oparta na zdarzeniach. Cloud Functions umożliwia pisanie kodu bez obaw o udostępnianie zasobów czy skalowanie pod kątem zmieniających się wymagań.

Istnieją 2 typy funkcji w Cloud Functions:

  • Funkcje HTTP odpowiadają na żądania HTTP. W ramach tego ćwiczenia z programowania utworzysz kilka takich narzędzi.
  • Funkcje działające w tle są wyzwalane przez zdarzenia, takie jak opublikowanie wiadomości w Cloud Pub/Sub lub przesłanie pliku do Cloud Storage. W tym module nie omawiamy tej kwestii, ale możesz dowiedzieć się więcej w dokumentacji.

efb3268e3b74ed4f.png

Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak utworzyć własne funkcje Cloud Functions w Pythonie.

Co utworzysz

W ramach tego ćwiczenia w Codelabs opublikujesz funkcję w Cloud Functions, która po wywołaniu przez HTTP wyświetla interfejs „Python Powered” logo:

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 Pythona, 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 wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. To ciąg znaków, który nie jest używany przez interfejsy API Google. W każdej chwili możesz ją zaktualizować.
  • Identyfikator projektu jest unikalny we wszystkich projektach Google Cloud i nie można go zmienić (po jego ustawieniu nie można go zmienić). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń w Codelabs musisz podać swój identyfikator projektu (zwykle identyfikowany jako PROJECT_ID). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować kolejny losowy. Możesz też spróbować własnych sił i sprawdzić, czy jest dostępna. Po wykonaniu tej czynności nie można jej już zmienić. Pozostanie ona przez cały czas trwania projektu.
  • Jest jeszcze trzecia wartość, numer projektu, z którego korzystają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Cloud/interfejsów API. Ukończenie tego ćwiczenia z programowania nic nie kosztuje. Aby wyłączyć zasoby w celu uniknięcia naliczania opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub projekt. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.

Uruchamianie Cloud Shell

Google Cloud można obsługiwać zdalnie z laptopa, ale w ramach tego ćwiczenia z programowania wykorzystasz Cloud Shell – środowisko wiersza poleceń działające w Cloud.

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 tej usługi. Jeśli wyświetlił się ekran pośredni, kliknij Dalej.

9c92662c6a846a5c.png

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

9f0e51b578fecce5.png

Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Większość zadań w ramach tego ćwiczenia z programowania można wykonać w przeglądarce.

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest uwierzytelniony, a projekt jest ustawiony na identyfikator Twojego projektu.

  1. Uruchom to polecenie w Cloud Shell, aby potwierdzić, że jesteś uwierzytelniony:
gcloud auth list

Dane wyjściowe polecenia

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

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Uruchom to polecenie w Cloud Shell, aby sprawdzić, czy polecenie gcloud zna Twój projekt:
gcloud config list project

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

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

gcloud config set project <PROJECT_ID>

Dane wyjściowe polecenia

Updated property [core/project].

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

Uruchom to polecenie w Cloud Shell, aby sprawdzić, czy interfejsy Cloud Functions i interfejsy Cloud Build API są włączone:

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

Uwaga: usługa Cloud Build zostanie wywołana 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

Potrzebujesz tych plików:

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

3. Przedstawiamy funkcje HTTP w Cloud Functions

Funkcje HTTP w Pythonie są zapisywane jako zwykłe funkcje w tym Pythonie. 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")

# ...

Plik możesz otworzyć w wybranym edytorze wiersza poleceń (nano, vim lub emacs). Możesz go też otworzyć 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 na temat opcji gcloud functions deploy:

  • --runtime: określa środowisko wykonawcze języka. W przypadku Pythona mogą 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 aktywują wykonanie funkcji.
  • --allow-unauthenticated: funkcja będzie publiczna i będzie mogła wykonywać wszystkie osoby wywołujące, bez sprawdzania uwierzytelniania.
  • Więcej informacji znajdziesz w artykule na temat gcloud Functions deploy.

Aby przetestować tę funkcję, możesz kliknąć adres URL httpsTrigger.url wyświetlony w wynikach polecenia powyżej. Możesz też pobrać adres URL 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. Jak pisać funkcję HTTP w Cloud Functions, która przyjmuje argumenty

Funkcje są bardziej uniwersalne, 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 wynikach polecenia powyżej. Możesz też pobrać adres URL 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 odpowiedź niestandardową:

Hello YOUR NAME! 🚀

Następnym krokiem jest dodanie testów jednostkowych, dzięki którym funkcje będą działać zgodnie z oczekiwaniami po zaktualizowaniu kodu źródłowego.

5. Testy pisania

Funkcje HTTP w Cloud Functions w Pythonie są testowane za pomocą modułu unittest z biblioteki standardowej. Aby przetestować funkcję, nie musisz uruchamiać emulatora ani innej symulacji – wystarczy zwykły kod Pythona.

Tak 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 języku Python zapisuje się w ten sam sposób co inne pliki tego typu. Zaczynają od zestawu operacji importu, a potem definiują klasy i funkcje.
  2. Deklaracja testu ma postać class TestHello(TestCase). Musi to być klasa, która dziedziczy wartość z pola unittest.TestCase.
  3. Klasa testowa zawiera metody, z których każda musi zaczynać się od test_ oznaczających poszczególne przypadki testowe.
  4. Każdy przypadek testowy testuje jedną z naszych funkcji, naśladując parametr request (czyli zastępując go fałszywym obiektem danymi wymaganymi do testu).
  5. Po wywołaniu każdej funkcji test sprawdza odpowiedź HTTP, by upewnić się, że jest zgodna z oczekiwaniami.

Ponieważ main.py zależy od flask, sprawdź, czy w środowisku testowym jest zainstalowana platforma Flask:

pip install flask

Zainstalowanie Flask daje wynik podobny do tego:

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

Przeprowadź te testy lokalnie:

python -m unittest

Te 3 testy jednostkowe powinny zakończyć się powodzeniem:

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

OK

Następnie utwórz nową funkcję, która zwróci zapytanie „Python Powered”, logo.

6. Jak napisać „Python Powered” Funkcja HTTP w Cloud Functions

Chcemy, aby nowa funkcja była nieco ciekawsza, zwracając funkcję „Python Powered”. obraz do każdego żądania:

a7aaf656b78050fd.png

Poniższa lista zawiera 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świetlony w danych wyjściowych polecenia powyżej. Jeśli wszystko będzie działać prawidłowo, zobaczysz komunikat „Python Powered” na nowej karcie przeglądarki.

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

7. Uruchamianie funkcji lokalnie

Funkcję HTTP można uruchomić lokalnie – wystarczy utworzyć aplikację internetową i wywołać funkcję w ramach trasy. Możesz go dodać w tym samym katalogu co Twoja funkcja. Plik o nazwie web_app.py zawiera następującą treść:

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 trasę pod podstawowym adresem URL, która jest obsługiwana przez funkcję o nazwie index().
  3. Następnie funkcja index() wywołuje naszą funkcję python_powered, przekazując do niej bieżące żądanie.

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

pip install flask

Zainstalowanie Flask daje 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 skorzystaj z podglądu w przeglądarce Cloud Shell, aby przetestować aplikację internetową w przeglądarce. W Cloud Shell kliknij „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 proxy. Podgląd w przeglądarce zezwala na dostęp przez HTTPS tylko do Twojego konta użytkownika. Jeśli wszystko działa prawidłowo, powinien wyświetlić się komunikat „Python Powered” logo.

8e5c3ead11cfd103.png

8. Gratulacje!

b158ce75c3cccd6d.png

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

Cennik Cloud Functions zależy od tego, jak często jest wywoływana Twoja funkcja, w tym poziom bezpłatny dla funkcji, które rzadko się uruchamiają. Po zakończeniu testowania funkcji w Cloud Functions możesz je usunąć przy użyciu gcloud:

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

Możesz też usunąć funkcje z konsoli Google Cloud.

Mamy nadzieję, że podoba Ci się korzystanie z Cloud Functions w Pythonie.