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

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

‫Cloud Storage דומה ל-Blobstore ב-App Engine בכך שאפשר להשתמש ב-Cloud Storage כדי להציג אובייקטים גדולים של נתונים (blobs), כמו קובצי וידאו או תמונות, ולאפשר למשתמשים להעלות קובצי נתונים גדולים. אפשר לגשת אל App Engine Blobstore רק דרך חבילת השירותים מדור קודם של App Engine, אבל Cloud Storage הוא מוצר עצמאי שאפשר לגשת אליו דרך ספריות הלקוח ב-Cloud. Google 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 ולהבין אותם:
  • יש לכם אפליקציית App Engine קיימת ב-Python 2 או ב-Python 3 שמשתמשת ב-Blobstore.
  • הדוגמאות במדריך הזה מציגות אפליקציה שמועברת ל-Cloud Storage באמצעות מסגרת Flask. שימו לב שאפשר להשתמש בכל מסגרת אינטרנט, כולל להישאר ב-webapp2, כשמבצעים מיגרציה ל-Cloud Storage.

סקירה כללית

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

  1. עדכון קובצי תצורה
  2. עדכון אפליקציית Python:
    • עדכון מסגרת האינטרנט
    • ייבוא והפעלה של Cloud Storage
    • עדכון של הגורמים שמטפלים ב-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 המיושן. תאריך סיום התמיכה ב-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.

עדכון של הגורמים שמטפלים ב-Blobstore

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

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

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

    ‫Python 2

    ‫Blobstore handlers ב-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)
    

דוגמאות

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