HTTP Cloud Functions ב-Python

1. מבוא

b158ce75c3cccd6d.png

‫Python היא שפת תכנות פופולרית בקוד פתוח שמשמשת מדעני נתונים, מפתחי אפליקציות אינטרנט, מנהלי מערכות ועוד.

‫Cloud Functions היא פלטפורמת מחשוב בלי שרת (serverless computing) שמונעת על ידי אירועים. בעזרת Cloud Functions אתם יכולים לכתוב את הקוד בלי לדאוג להקצאת משאבים או להתאמה לעומס (scaling) כדי לטפל בדרישות משתנות.

יש שני סוגים של פונקציות Cloud Functions:

  • פונקציות HTTP מגיבות לבקשות HTTP. ב-Codelab הזה תבנו כמה כאלה.
  • פונקציות ברקע מופעלות על ידי אירועים, כמו פרסום הודעה ב-Cloud Pub/Sub או העלאת קובץ ל-Cloud Storage. אנחנו לא מתייחסים לזה בשיעור ה-Lab הזה, אבל אתם יכולים לקרוא מידע נוסף במסמכי התיעוד.

efb3268e3b74ed4f.png

ב-Codelab הזה נסביר איך ליצור פונקציות Cloud Functions משלכם ב-Python.

מה תפתחו

ב-Codelab הזה תפרסמו פונקציה של 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

  • שם הפרויקט הוא השם שמוצג למשתתפים בפרויקט. זוהי מחרוזת תווים שלא משמשת את Google APIs. תמיד אפשר לעדכן אותו.
  • מזהה הפרויקט הוא ייחודי לכל הפרויקטים ב-Google Cloud, ואי אפשר לשנות אותו אחרי שהוא הוגדר. מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית. בדרך כלל לא משנה מה היא. ברוב ה-Codelabs, תצטרכו להפנות למזהה הפרויקט (בדרך כלל מזוהה בתור PROJECT_ID). אם אתם לא אוהבים את המזהה שנוצר, אתם יכולים ליצור מזהה אקראי אחר. אתם יכולים גם לנסות להזין שם משלכם ולבדוק אם הוא זמין. אי אפשר לשנות אותו אחרי השלב הזה, והוא נשאר לאורך כל הפרויקט.
  • לידיעתכם, יש ערך שלישי, מספר פרויקט, שמשמש בחלק מממשקי ה-API. מידע נוסף על שלושת הערכים האלה זמין במסמכי התיעוד.
  1. בשלב הבא, צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי ה-API של Cloud. העלות של הפעלת ה-Codelab הזה תהיה נמוכה מאוד, אם בכלל. כדי להשבית את המשאבים ולהימנע מחיובים נוספים, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט. משתמשים חדשים ב-Google Cloud זכאים לתקופת ניסיון בחינם בשווי 300$‎.

הפעלת Cloud Shell

אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה תשתמשו ב-Cloud Shell, סביבה של שורת פקודה שפועלת בענן.

הפעלת Cloud Shell

  1. במסוף Cloud, לוחצים על Activate Cloud Shell 853e55310c205094.png.

3c1dabeca90e44e5.png

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג מסך ביניים שמתאר את הכלי. אם מוצג מסך ביניים, לוחצים על Continue.

9c92662c6a846a5c.png

ההקצאה וההתחברות ל-Cloud Shell אמורות להימשך כמה רגעים בלבד.

9f0e51b578fecce5.png

המכונה הווירטואלית הזו נטענת עם כל הכלים למפתחים שצריך. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את ביצועי הרשת והאימות. את רוב העבודה ב-Codelab הזה, אם לא את כולה, אפשר לעשות בדפדפן.

אחרי שתתחברו ל-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. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט:
gcloud config list project

פלט פקודה

[core]
project = <PROJECT_ID>

אם הוא לא מוגדר, אפשר להגדיר אותו באמצעות הפקודה הבאה:

gcloud config set project <PROJECT_ID>

פלט פקודה

Updated property [core/project].

מוודאים שממשקי ה-API של Cloud Functions ו-Cloud Build מופעלים

מריצים את הפקודה הבאה מ-Cloud Shell כדי לוודא שהממשקים Cloud Functions API ו-Cloud Build API מופעלים:

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

הערה: הפקודה gcloud functions deploy תפעיל את Cloud Build, והקוד שלכם יומר באופן אוטומטי לקובץ אימג' של קונטיינר.

הורדת קוד המקור

מריצים את הפקודות הבאות מהמסוף של 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 Editor אחרי שמגדירים את ספריית קובצי המקור כסביבת עבודה:

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 framework מותקן בסביבת הבדיקה:

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 framework מותקן בסביבת הפיתוח:

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:

6c9ff9e5c692c58e.gif

‫Cloud Shell פותח את כתובת ה-URL של התצוגה המקדימה בשירות ה-proxy שלו בחלון דפדפן חדש. התצוגה המקדימה באינטרנט מגבילה את הגישה דרך HTTPS לחשבון המשתמש שלכם בלבד. אם הכל פועל כמו שצריך, אמור להופיע הלוגו "Python Powered".

8e5c3ead11cfd103.png

8. מעולה!

b158ce75c3cccd6d.png

פרסתם פונקציות HTTP של Cloud Functions באמצעות פונקציות אידיומטיות שמטפלות בבקשות לאחזור מהרשת באמצעות Flask framework.

התמחור של 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.