העברה מ-App Engine Blobstore ל-Cloud Storage

במדריך הזה מוסבר איך להעביר נתונים מ-App Engine Blobstore ל-Cloud Storage.

‫Cloud Storage דומה ל-Blobstore של App Engine בכך שאפשר להשתמש בו כדי להציג אובייקטים גדולים של נתונים (blobs), כמו קובצי וידאו או תמונות, ולאפשר למשתמשים להעלות קובצי נתונים גדולים. בעוד ש-Blobstore של App Engine נגיש רק דרך חבילת השירותים מדור קודם של App Engine,‏ Cloud Storage הוא מוצר עצמאי Google Cloudשאפשר לגשת אליו דרך ספריות הלקוח של Cloud. ‏ Cloud Storage מציע לאפליקציה שלכם פתרון מודרני יותר לאחסון אובייקטים, ומאפשר לכם גמישות במעבר ל-Cloud Run או לפלטפורמה אחרת לאירוח אפליקציות Google Cloud בשלב מאוחר יותר.

ב Google Cloud פרויקטים שנוצרו אחרי נובמבר 2016, Blobstore משתמש בקטגוריות של Cloud Storage מאחורי הקלעים. כלומר, כשמעבירים את האפליקציה ל-Cloud Storage, כל האובייקטים וההרשאות הקיימים בקטגוריות הקיימות של Cloud Storage נשארים ללא שינוי. אפשר גם להתחיל לגשת לקטגוריות הקיימות האלה באמצעות ספריות הלקוח של Cloud Storage.

ההבדלים והדמיון העיקריים

‫Cloud Storage לא כולל את התלות והמגבלות הבאות של Blobstore:

  • ל-Blobstore API ל-Python 2 יש תלות ב-webapp.
  • ב-Blobstore API ל-Python 3 נעשה שימוש במחלקות כלי עזר כדי להשתמש במטפלים ב-Blobstore.
  • ב-Blobstore, המספר המקסימלי של קבצים שאפשר להעלות הוא 500. אין הגבלה על מספר האובייקטים שאפשר ליצור בקטגוריה של Cloud Storage.

‫Cloud Storage לא תומך ב:

  • מחלקות של תוכניות לטיפול ב-Blobstore
  • אובייקטים ב-Blobstore

הדמיון בין Cloud Storage לבין Blobstore של App Engine:

  • יכולת לקרוא ולכתוב אובייקטים גדולים של נתונים בסביבת זמן ריצה, וגם לאחסן ולספק אובייקטים סטטיים גדולים של נתונים, כמו סרטים, תמונות או תוכן סטטי אחר. גודל האובייקט המקסימלי ב-Cloud Storage הוא 5TiB.
  • מאפשרת לכם לאחסן אובייקטים בקטגוריה של Cloud Storage.
  • יש להם תוכנית בחינם.

לפני שמתחילים

  • חשוב לעיין בתמחור ובמכסות של Cloud Storage ולהבין אותם:
  • יש לכם אפליקציית Python 2 או Python 3 App Engine קיימת שמשתמשת ב-Blobstore.
  • הדוגמאות במדריך הזה מציגות אפליקציה שעוברת ל-Cloud Storage באמצעות מסגרת Flask. שימו לב שאפשר להשתמש בכל מסגרת אינטרנט, כולל להישאר ב-webapp2, כשמבצעים מיגרציה ל-Cloud Storage.

סקירה כללית

באופן כללי, תהליך ההעברה מ-Blobstore של App Engine ל-Cloud Storage מורכב מהשלבים הבאים:

  1. עדכון קובצי תצורה
  2. עדכון אפליקציית Python:
    • עדכון מסגרת האינטרנט
    • ייבוא והפעלה של Cloud Storage
    • עדכון של רכיבי handler של Blobstore
    • אופציונלי: מעדכנים את מודל הנתונים אם משתמשים ב-Cloud NDB או ב-App Engine NDB
  3. בדיקה ופריסה של האפליקציה

עדכון קובצי תצורה

לפני שמשנים את קוד האפליקציה כדי לעבור מ-Blobstore ל-Cloud Storage, צריך לעדכן את קובצי ההגדרות כדי להשתמש בספריית Cloud Storage.

  1. מעדכנים את הקובץ app.yaml. פועלים לפי ההוראות שמתאימות לגרסת Python:

    ‫Python 2

    באפליקציות Python 2:

    1. מסירים את הקטע handlers ואת כל התלויות המיותרות של אפליקציית האינטרנט בקטע libraries.
    2. אם אתם משתמשים בספריות לקוח ב-Cloud, אתם צריכים להוסיף את הגרסאות העדכניות של הספריות grpcio ו-setuptools.
    3. מוסיפים את הספרייה ssl כי היא נדרשת על ידי Cloud Storage.

    קובץ app.yaml לדוגמה עם השינויים שבוצעו:

    runtime: python27
    threadsafe: yes
    api_version: 1
    
    handlers:
    - url: /.*
      script: main.app
    
    libraries:
    - name: grpcio
      version: latest
    - name: setuptools
      version: latest
    - name: ssl
      version: latest
    

    ‫Python 3

    באפליקציות Python 3, מוחקים את כל השורות חוץ מהרכיב runtime. לדוגמה:

    runtime: python310 # or another support version
    

    סביבת זמן הריצה של Python 3 מתקינה ספריות באופן אוטומטי, כך שלא צריך לציין ספריות מובנות מסביבת זמן הריצה הקודמת של Python 2. אם האפליקציה שלכם ב-Python 3 משתמשת בשירותים אחרים מדור קודם שצורפו לחבילה כשאתם מעבירים אותה ל-Cloud Storage, אל תשנו את הקובץ app.yaml.

  2. מעדכנים את הקובץ requirements.txt. פועלים לפי ההוראות שמתאימות לגרסת Python:

    ‫Python 2

    מוסיפים את ספריות הלקוח של Cloud Storage לרשימת התלויות בקובץ requirements.txt.

    google-cloud-storage
    

    לאחר מכן מריצים את הפקודה pip install -t lib -r requirements.txt כדי לעדכן את רשימת הספריות שזמינות לאפליקציה.

    ‫Python 3

    מוסיפים את ספריות הלקוח של Cloud Storage לרשימת התלויות בקובץ requirements.txt.

    google-cloud-storage
    

    ‫App Engine מתקין את התלות האלה באופן אוטומטי במהלך פריסת האפליקציה בסביבת זמן הריצה של Python 3, לכן צריך למחוק את התיקייה lib אם היא קיימת.

  3. באפליקציות Python 2, אם האפליקציה משתמשת בספריות מובנות או בספריות שהועתקו, צריך לציין את הנתיבים האלה בקובץ appengine_config.py:

    import pkg_resources
    from google.appengine.ext import vendor
    
    # Set PATH to your libraries folder.
    PATH = 'lib'
    # Add libraries installed in the PATH folder.
    vendor.add(PATH)
    # Add libraries to pkg_resources working set to find the distribution.
    pkg_resources.working_set.add_entry(PATH)
    

עדכון אפליקציית Python

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

עדכון של מסגרת אינטרנט ב-Python 2

לאפליקציות Python 2 שמשתמשות ב-framework‏ webapp2, מומלץ לבצע מיגרציה מ-framework‏ webapp2 המיושן. אפשר לעיין בלוח הזמנים של התמיכה ב-Runtime כדי לראות את תאריך סיום התמיכה ב-Python 2.

אפשר להעביר את האתר ל-framework אחר לאינטרנט, כמו Flask, ‏ Django או WSGI. מכיוון ש-Cloud Storage לא כולל תלות ב-webapp2, ומטפלים ב-Blobstore לא נתמכים, אפשר למחוק או להחליף ספריות אחרות שקשורות לאפליקציות אינטרנט.

אם תבחרו להמשיך להשתמש ב-webapp2, שימו לב שהדוגמאות במדריך הזה משתמשות ב-Cloud Storage עם Flask.

אם אתם מתכננים להשתמש בשירותים Google Cloud בנוסף ל-Cloud Storage, או לקבל גישה לגרסאות העדכניות ביותר של זמן הריצה, כדאי לשדרג את האפליקציה לזמן הריצה של Python 3. מידע נוסף מופיע במאמר סקירה כללית על המעבר מ-Python 2 ל-Python 3.

ייבוא והפעלה של Cloud Storage

משנים את קובצי האפליקציה על ידי עדכון שורות הייבוא וההפעלה:

  1. מסירים הצהרות ייבוא של Blobstore, כמו אלה:

    import webapp2
    from google.appengine.ext import blobstore
    from google.appengine.ext.webapp import blobstore_handlers
    
  2. מוסיפים את הצהרות הייבוא של Cloud Storage ושל ספריות האימות של Google, כמו בדוגמה הבאה:

    import io
    from flask import (Flask, abort, redirect, render_template,
    request, send_file, url_for)
    from google.cloud import storage
    import google.auth
    

    צריך את ספריית האימות של Google כדי לקבל את אותו מזהה פרויקט ששימש ב-Blobstore עבור Cloud Storage. מייבאים ספריות אחרות כמו Cloud NBD אם הן רלוונטיות לאפליקציה.

  3. יוצרים לקוח חדש ל-Cloud Storage ומציינים את הקטגוריה שבה נעשה שימוש ב-Blobstore. לדוגמה:

    gcs_client = storage.Client()
    _, PROJECT_ID = google.auth.default()
    BUCKET = '%s.appspot.com' % PROJECT_ID
    

    ב Google Cloud פרויקטים שנוצרו אחרי נובמבר 2016, מערכת Blobstore כותבת לקטגוריה של Cloud Storage שנקראת על שם כתובת ה-URL של האפליקציה, והיא פועלת לפי הפורמט PROJECT_ID.appspot.com. משתמשים באימות של Google כדי לקבל את מזהה הפרויקט, כדי לציין את קטגוריית Cloud Storage שמשמשת לאחסון של אובייקטים בינאריים ב-Blobstore.

עדכון של רכיבי handler של Blobstore

מכיוון ש-Cloud Storage לא תומך ב-handlers של העלאה והורדה של Blobstore, צריך להשתמש בשילוב של פונקציונליות של Cloud Storage, מודול של ספרייה רגילה, מסגרת האינטרנט וכלי Python כדי להעלות ולהוריד אובייקטים (blobs) ב-Cloud Storage.io

בדוגמה הבאה מוצגות דרכים לעדכן את רכיבי ה-handler של Blobstore באמצעות Flask כמסגרת האינטרנט לדוגמה:

  1. מחליפים את מחלקות הטיפול בהעלאות של Blobstore בפונקציית העלאה ב-Flask. פועלים לפי ההוראות לגרסת Python שבה אתם משתמשים:

    ‫Python 2

    ‫handlers ב-Blobstore ב-Python 2 הם מחלקות webapp2 כמו בדוגמה הבאה של Blobstore:

    class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
        'Upload blob (POST) handler'
        def post(self):
            uploads = self.get_uploads()
            blob_id = uploads[0].key() if uploads else None
            store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
            self.redirect('/', code=307)
    ...
    app = webapp2.WSGIApplication([
        ('/', MainHandler),
        ('/upload', UploadHandler),
        ('/view/([^/]+)?', ViewBlobHandler),
    ], debug=True)
    

    כדי להשתמש ב-Cloud Storage:

    1. מחליפים את מחלקת ההעלאה של אפליקציית האינטרנט בפונקציית העלאה של Flask.
    2. מחליפים את upload handler ואת הניתוב בשיטת Flask POST עם קישוט של ניתוב.

    דוגמת קוד מעודכנת:

    @app.route('/upload', methods=['POST'])
    def upload():
        'Upload blob (POST) handler'
        fname = None
        upload = request.files.get('file', None)
        if upload:
            fname = secure_filename(upload.filename)
            blob = gcs_client.bucket(BUCKET).blob(fname)
            blob.upload_from_file(upload, content_type=upload.content_type)
        store_visit(request.remote_addr, request.user_agent, fname)
        return redirect(url_for('root'), code=307)
    

    בדוגמת הקוד המעודכנת של Cloud Storage, האפליקציה מזהה עכשיו את ארטיפקטים של אובייקטים לפי שם האובייקט (fname) במקום blob_id. ניתוב מתבצע גם בחלק התחתון של קובץ האפליקציה.

    כדי לקבל את האובייקט שהועלה, מחליפים את השיטה get_uploads() של Blobstore בשיטה request.files.get() של Flask. ב-Flask, אפשר להשתמש בשיטה secure_filename() כדי לקבל שם בלי תווי נתיב, כמו /, עבור הקובץ, ולזהות את האובייקט באמצעות gcs_client.bucket(BUCKET).blob(fname) כדי לציין את שם הקטגוריה ואת שם האובייקט.

    הקריאה ל-Cloud Storage‏ upload_from_file() מבצעת את ההעלאה כמו שמוצג בדוגמה המעודכנת.

    ‫Python 3

    מחלקת הטיפול בהעלאות ב-Blobstore ל-Python 3 היא מחלקת כלי עזר, ונדרש שימוש במילון WSGI environ כפרמטר קלט, כמו בדוגמה הבאה של Blobstore:

    class UploadHandler(blobstore.BlobstoreUploadHandler):
        'Upload blob (POST) handler'
        def post(self):
            uploads = self.get_uploads(request.environ)
            if uploads:
                blob_id = uploads[0].key()
                store_visit(request.remote_addr, request.user_agent, blob_id)
            return redirect('/', code=307)
    ...
    @app.route('/upload', methods=['POST'])
    def upload():
        """Upload handler called by blobstore when a blob is uploaded in the test."""
        return UploadHandler().post()
    

    כדי להשתמש ב-Cloud Storage, מחליפים את השיטה get_uploads(request.environ) של Blobstore בשיטה request.files.get() של Flask.

    דוגמת קוד מעודכנת:

    @app.route('/upload', methods=['POST'])
    def upload():
        'Upload blob (POST) handler'
        fname = None
        upload = request.files.get('file', None)
        if upload:
            fname = secure_filename(upload.filename)
            blob = gcs_client.bucket(BUCKET).blob(fname)
            blob.upload_from_file(upload, content_type=upload.content_type)
        store_visit(request.remote_addr, request.user_agent, fname)
        return redirect(url_for('root'), code=307)
    

    בדוגמת הקוד המעודכנת של Cloud Storage, האפליקציה מזהה עכשיו את ארטיפקטים של אובייקטים לפי שם האובייקט (fname) במקום blob_id. ניתוב מתבצע גם בחלק התחתון של קובץ האפליקציה.

    כדי לקבל את האובייקט שהועלה, מחליפים את השיטה get_uploads() של Blobstore בשיטה request.files.get() של Flask. ב-Flask, אפשר להשתמש בשיטה secure_filename() כדי לקבל שם בלי תווי נתיב, כמו /, עבור הקובץ, ולזהות את האובייקט באמצעות gcs_client.bucket(BUCKET).blob(fname) כדי לציין את שם הקטגוריה ואת שם האובייקט.

    השיטה upload_from_file() של Cloud Storage מבצעת את ההעלאה כמו בדוגמה המעודכנת.

  2. מחליפים את מחלקות ה-handler של ההורדה ב-Blobstore בפונקציית הורדה ב-Flask. פועלים לפי ההוראות שמתאימות לגרסת Python:

    ‫Python 2

    בדוגמה הבאה של handler להורדה מוצג השימוש במחלקה BlobstoreDownloadHandler, שמשתמשת ב-webapp2:

    class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
        'view uploaded blob (GET) handler'
        def get(self, blob_key):
            self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)
    ...
    app = webapp2.WSGIApplication([
        ('/', MainHandler),
        ('/upload', UploadHandler),
        ('/view/([^/]+)?', ViewBlobHandler),
    ], debug=True)
    

    כדי להשתמש ב-Cloud Storage:

    1. מעדכנים את השיטה send_blob() של Blobstore לשימוש בשיטה download_as_bytes() של Cloud Storage.
    2. שינוי הניתוב מ-webapp2 ל-Flask.

    דוגמת קוד מעודכנת:

    @app.route('/view/<path:fname>')
    def view(fname):
        'view uploaded blob (GET) handler'
        blob = gcs_client.bucket(BUCKET).blob(fname)
        try:
            media = blob.download_as_bytes()
        except exceptions.NotFound:
            abort(404)
        return send_file(io.BytesIO(media), mimetype=blob.content_type)
    

    בדוגמת הקוד המעודכנת של Cloud Storage, ‏ Flask מעטר את הנתיב בפונקציית Flask ומזהה את האובייקט באמצעות '/view/<path:fname>'. ‫Cloud Storage מזהה את האובייקט blob לפי שם האובייקט ושם הקטגוריה, ומשתמש בשיטה download_as_bytes() כדי להוריד את האובייקט כבייטים, במקום להשתמש בשיטה send_blob מ-Blobstore. אם הארטיפקט לא נמצא, האפליקציה מחזירה שגיאת HTTP 404.

    ‫Python 3

    בדומה ל-upload handler, המחלקה download handler ב-Blobstore ל-Python 3 היא מחלקה של כלי עזר, ונדרש שימוש במילון WSGI environ כפרמטר קלט, כמו בדוגמה הבאה של Blobstore:

    class ViewBlobHandler(blobstore.BlobstoreDownloadHandler):
        'view uploaded blob (GET) handler'
        def get(self, blob_key):
            if not blobstore.get(blob_key):
                return "Photo key not found", 404
            else:
                headers = self.send_blob(request.environ, blob_key)
    
            # Prevent Flask from setting a default content-type.
            # GAE sets it to a guessed type if the header is not set.
            headers['Content-Type'] = None
            return '', headers
    ...
    @app.route('/view/<blob_key>')
    def view_photo(blob_key):
        """View photo given a key."""
        return ViewBlobHandler().get(blob_key)
    

    כדי להשתמש ב-Cloud Storage, צריך להחליף את send_blob(request.environ, blob_key) של Blobstore בשיטה blob.download_as_bytes() של Cloud Storage.

    דוגמת קוד מעודכנת:

    @app.route('/view/<path:fname>')
    def view(fname):
        'view uploaded blob (GET) handler'
        blob = gcs_client.bucket(BUCKET).blob(fname)
        try:
            media = blob.download_as_bytes()
        except exceptions.NotFound:
            abort(404)
        return send_file(io.BytesIO(media), mimetype=blob.content_type)
    

    בדוגמת הקוד המעודכנת של Cloud Storage,‏ blob_key מוחלף ב-fname, ו-Flask מזהה את האובייקט באמצעות כתובת ה-URL‏ '/view/<path:fname>'. השיטה gcs_client.bucket(BUCKET).blob(fname) משמשת לאיתור שם הקובץ ושם הקטגוריה. השיטה download_as_bytes() של Cloud Storage מורידה את האובייקט כבייטים, במקום להשתמש בשיטה send_blob() מ-Blobstore.

  3. אם האפליקציה משתמשת ב-handler ראשי, צריך להחליף את המחלקה MainHandler בפונקציה root() ב-Flask. פועלים לפי ההוראות לגרסה של Python שבה משתמשים:

    ‫Python 2

    הדוגמה הבאה מראה איך להשתמש במחלקה MainHandler של Blobstore:

    class MainHandler(BaseHandler):
        'main application (GET/POST) handler'
        def get(self):
            self.render_response('index.html',
                    upload_url=blobstore.create_upload_url('/upload'))
    
        def post(self):
            visits = fetch_visits(10)
            self.render_response('index.html', visits=visits)
    
    app = webapp2.WSGIApplication([
        ('/', MainHandler),
        ('/upload', UploadHandler),
        ('/view/([^/]+)?', ViewBlobHandler),
    ], debug=True)
    

    כדי להשתמש ב-Cloud Storage:

    1. מסירים את המחלקה MainHandler(BaseHandler), כי Flask מטפל בניתוב בשבילכם.
    2. פישוט הקוד של Blobstore באמצעות Flask.
    3. מסירים את הניתוב של אפליקציית האינטרנט בסוף.

    דוגמת קוד מעודכנת:

    @app.route('/', methods=['GET', 'POST'])
    def root():
        'main application (GET/POST) handler'
        context = {}
        if request.method == 'GET':
            context['upload_url'] = url_for('upload')
        else:
            context['visits'] = fetch_visits(10)
        return render_template('index.html', **context)
    

    ‫Python 3

    אם השתמשתם ב-Flask, לא יהיה לכם מחלקה MainHandler, אבל אם נעשה שימוש ב-blobstore, צריך לעדכן את פונקציית השורש של Flask. בדוגמה הבאה נעשה שימוש בפונקציה blobstore.create_upload_url('/upload'):

    @app.route('/', methods=['GET', 'POST'])
    def root():
        'main application (GET/POST) handler'
        context = {}
        if request.method == 'GET':
            context['upload_url'] = blobstore.create_upload_url('/upload')
        else:
            context['visits'] = fetch_visits(10)
        return render_template('index.html', **context)
    

    כדי להשתמש ב-Cloud Storage, מחליפים את הפונקציה blobstore.create_upload_url('/upload') בשיטה url_for() של Flask כדי לקבל את כתובת ה-URL של הפונקציה upload().

    דוגמת קוד מעודכנת:

    @app.route('/', methods=['GET', 'POST'])
    def root():
        'main application (GET/POST) handler'
        context = {}
        if request.method == 'GET':
            context['upload_url'] = url_for('upload') # Updated to use url_for
        else:
            context['visits'] = fetch_visits(10)
        return render_template('index.html', **context)
    

בדיקה ופריסה של האפליקציה

שרת הפיתוח המקומי מאפשר לכם לבדוק שהאפליקציה פועלת, אבל לא תוכלו לבדוק את Cloud Storage עד שתפרסו גרסה חדשה, כי כל הבקשות ל-Cloud Storage צריכות להישלח דרך האינטרנט לקטגוריה של Cloud Storage בפועל. במאמר בדיקה ופריסה של האפליקציה מוסבר איך להריץ את האפליקציה באופן מקומי. לאחר מכן פורסים גרסה חדשה כדי לוודא שהאפליקציה נראית כמו קודם.

אפליקציות שמשתמשות ב-App Engine NDB או ב-Cloud NDB

אם האפליקציה שלכם משתמשת ב-App Engine NDB או ב-Cloud NDB, אתם צריכים לעדכן את מודל הנתונים של Datastore כדי לכלול מאפיינים שקשורים ל-Blobstore.

עדכון מודל הנתונים

מאחר שהמאפיינים BlobKey מ-NDB לא נתמכים ב-Cloud Storage, צריך לשנות את השורות שקשורות ל-Blobstore כדי להשתמש במקבילות מובנות מ-NDB, ממסגרות אינטרנט או ממקומות אחרים.

כדי לעדכן את מודל הנתונים:

  1. מחפשים את השורות שמשתמשות ב-BlobKey במודל הנתונים, כמו השורה הבאה:

    class Visit(ndb.Model):
        'Visit entity registers visitor IP address & timestamp'
        visitor   = ndb.StringProperty()
        timestamp = ndb.DateTimeProperty(auto_now_add=True)
        file_blob = ndb.BlobKeyProperty()
    
  2. מחליפים את ndb.BlobKeyProperty() ב-ndb.StringProperty():

    class Visit(ndb.Model):
        'Visit entity registers visitor IP address & timestamp'
        visitor   = ndb.StringProperty()
        timestamp = ndb.DateTimeProperty(auto_now_add=True)
        file_blob = ndb.StringProperty() # Modified from ndb.BlobKeyProperty()
    
  3. אם אתם גם משדרגים מ-App Engine NDB ל-Cloud NDB במהלך ההעברה, כדאי לעיין במדריך להעברה ל-Cloud NDB כדי לקבל הנחיות לגבי שינוי מבנה הקוד של NDB כך שישתמש במנהלי הקשר של Python.

תאימות לאחור של מודל הנתונים של Datastore

בקטע הקודם, החלפת ndb.BlobKeyProperty ב-ndb.StringProperty גרמה לאפליקציה להיות לא תואמת לאחור, כלומר האפליקציה לא תוכל לעבד רשומות ישנות יותר שנוצרו על ידי Blobstore. אם אתם צריכים לשמור נתונים ישנים, אתם יכולים ליצור שדה נוסף לרשומות חדשות ב-Cloud Storage במקום לעדכן את השדה ndb.BlobKeyProperty, וליצור פונקציה לנרמול הנתונים.

בדוגמאות שבקטעים הקודמים, מבצעים את השינויים הבאים:

  1. כשמגדירים את מודל הנתונים, יוצרים שני שדות נפרדים של נכס. משתמשים במאפיין file_blob כדי לזהות אובייקטים שנוצרו על ידי Blobstore, ובמאפיין file_gcs כדי לזהות אובייקטים שנוצרו על ידי Cloud Storage:

    class Visit(ndb.Model):
        'Visit entity registers visitor IP address & timestamp'
        visitor   = ndb.StringProperty()
        timestamp = ndb.DateTimeProperty(auto_now_add=True)
        file_blob = ndb.BlobKeyProperty()  # backwards-compatibility
        file_gcs  = ndb.StringProperty()
    
  2. מחפשים את השורות שמתייחסות לביקורים חדשים, כמו השורות הבאות:

    def store_visit(remote_addr, user_agent, upload_key):
        'create new Visit entity in Datastore'
        with ds_client.context():
            Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                    file_blob=upload_key).put()
    
  3. משנים את הקוד כך ש-file_gcs ישמש לערכים מהזמן האחרון. לדוגמה:

    def store_visit(remote_addr, user_agent, upload_key):
        'create new Visit entity in Datastore'
        with ds_client.context():
            Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                    file_gcs=upload_key).put() # change file_blob to file_gcs for new requests
    
  4. יוצרים פונקציה חדשה לנרמול הנתונים. בדוגמה הבאה מוצג השימוש ב-ETL (חילוץ, טרנספורמציה וטעינה) כדי לבצע לולאה בכל הביקורים, ולקחת את נתוני המבקר וחותמת הזמן כדי לבדוק אם קיים file_gcs או file_gcs:

    def etl_visits(visits):
        return [{
                'visitor': v.visitor,
                'timestamp': v.timestamp,
                'file_blob': v.file_gcs if hasattr(v, 'file_gcs') \
                        and v.file_gcs else v.file_blob
                } for v in visits]
    
  5. מחפשים את השורה שמפנה לפונקציה fetch_visits():

    @app.route('/', methods=['GET', 'POST'])
    def root():
        'main application (GET/POST) handler'
        context = {}
        if request.method == 'GET':
            context['upload_url'] = url_for('upload')
        else:
            context['visits'] = fetch_visits(10)
        return render_template('index.html', **context)
    
  6. מציבים את fetch_visits() בתוך הפונקציה etl_visits(), לדוגמה:

    @app.route('/', methods=['GET', 'POST'])
    def root():
        'main application (GET/POST) handler'
        context = {}
        if request.method == 'GET':
            context['upload_url'] = url_for('upload')
        else:
            context['visits'] = etl_visits(fetch_visits(10)) # etl_visits wraps around fetch_visits
        return render_template('index.html', **context)
    

דוגמאות

המאמרים הבאים