גישה לשירותים מדור קודם בחבילה ל-Python 3

בדף הזה מוסבר איך להתקין ולהשתמש בשירותים מדור קודם שצורפו ל-Python 3 runtime בסביבה הרגילה. האפליקציה שלכם חייבת לגשת לשירותים הכלולים בחבילה באמצעות App Engine services SDK ל-Python 3.

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

התקנה של App Engine services SDK

כדי להתקין את App Engine services SDK, פועלים לפי השלבים הבאים:

  1. כדי לכלול את ה-SDK באפליקציה, מוסיפים את השורה הבאה לקובץ requirements.txt:

    appengine-python-standard>=1.0.0
    

    ערכת ה-SDK זמינה במאגר appengine-python-standard ב-GitHub וב-PyPI.

  2. מוסיפים את הקוד הבא לסקריפט ה-Python הראשי. הקוד הזה יוצר תוכנת ביניים של WSGI שמגדירה את המשתנים שנדרשים להפעלת קריאות ה-API.

    בקבוקון לשתייה חריפה

    from flask import Flask
    from google.appengine.api import wrap_wsgi_app
    
    app = Flask(__name__)
    app.wsgi_app = wrap_wsgi_app(app.wsgi_app)
    

    Django

    from DJANGO_PROJECT_NAME.wsgi import application
    from google.appengine.api import wrap_wsgi_app
    
    app = wrap_wsgi_app(application)
    

    פירמידה

    from pyramid.config import Configurator
    from google.appengine.api import wrap_wsgi_app
    
    config = Configurator()
    # make configuration settings
    app = config.make_wsgi_app()
    app = wrap_wsgi_app(app)
    

    WSGI

    import google.appengine.api
    
    def app(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/plain')])
        yield b'Hello world!\n'
    
    app = google.appengine.api.wrap_wsgi_app(app)
    
  3. מעדכנים את קובץ app.yaml כדי לציין שירות אחד או יותר מהדור הקודם. לדוגמה:

    app_engine_bundled_services:
    - datastore_v3
    - memcache
    - user
    

    אם אתם משתמשים בתכונות כמו login: admin בקטע handlers של קובץ app.yaml, אתם צריכים להפעיל את Users API על ידי הגדרת ההגדרה user ברשימה app_engine_bundled_services.

  4. כדי לפרוס את האפליקציה, משתמשים בפקודה gcloud app deploy.

שיקולים לגבי מיגרציה

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

בדיקה

כדי לבדוק באופן מקומי את הפונקציונליות של שירותים בחבילה מדור קודם באפליקציית Python 3, צריך להשתמש בשרת פיתוח מקומי. כשמריצים את הפקודה dev_appserver.py, צריך להגדיר את הארגומנט --runtime_python_path כך שיכלול נתיב לפרשן Python 3. לדוגמה:

   python3 CLOUD_SDK_ROOT/bin/dev_appserver.py --runtime_python_path=/usr/bin/python3

אפשר גם להגדיר את הארגומנט כרשימה מופרדת בפסיקים של זוגות [RUNTIME_ID]=[PYTHON_INTERPRETER_PATH]. לדוגמה:

   python3 CLOUD_SDK_ROOT/bin/dev_appserver.py --runtime_python_path="python27=/user/bin/python2.7,python3=/usr/bin/python3"

תאימות ל-Pickle

שירותים משותפים, כולל Memcache,‏ Cloud NDB וdeferred, משתמשים במודול pickle כדי לבצע סריאליזציה של אובייקטים של Python ולשתף אותם. אם בסביבת App Engine שלכם נעשה שימוש גם ב-Python 2 וגם ב-Python 3, כמו שקורה בדרך כלל במהלך העברה, אתם צריכים לוודא שאפשר לשחזר אובייקטים משותפים שעברו סריאליזציה ונכתבו על ידי גרסה אחת של Python על ידי הגרסה השנייה. במדריך מפורטות הנחיות להטמעת תאימות בין גרסאות שונות של pickle.

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

  • באפליקציות שמשתמשות ב-Memcache, כולל אפליקציות שמשתמשות ב-NDB, צריך להגדיר: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • באפליקציות שמשתמשות ב-NDB כדי להתחבר ל-Datastore, צריך להגדיר: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • באפליקציות שמשתמשות בהתקנה מושהית, מגדירים: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

ב-Python 2, אובייקטים מסוג string מכילים רצף של ערכי בייט של 8 ביט. ב-Python 3, אובייקטים מסוג string מכילים רצף של תווי Unicode. כברירת מחדל, הפונקציה pickle ב-Python 3 מתרגמת string ב-Python 2 ל-Unicode על ידי פירוש string ב-Python 3 כ-ASCII. זה עלול לגרום לשגיאות בערכים שנמצאים מחוץ לטווח התווים של ASCII, כלומר מ-0 עד 127. ‏ Memcache תומך בביטול המיפוי הזה שמוגדר כברירת מחדל.

from google.appengine.api import memcache
import six.moves.cPickle as pickle

def _unpickle_factory(file):
    return pickle.Unpickler(file, encoding='latin1')

memcache.setup_client(memcache.Client(unpickler=_unpickle_factory))

הקידוד latin1 מגדיר מיפוי לכל אחד מ-256 הערכים האפשריים של כל בייט ב-string של Python 2. כך נמנעים שגיאות פענוח. עם זאת, אם string של Python 2 מכיל נתוני Unicode בפועל מחוץ לטווח latin1, כמו נתונים שנקראו מקובץ, cPickle לא ימפה את הנתונים בצורה נכונה. לכן, חשוב לעדכן את הקוד של Python 2 כך שיכיל נתוני Unicode עם אובייקטים מסוג unicode ולא אובייקטים מסוג string, עבור אובייקטים שאתם מבצעים עליהם Pickling. במדריך בנושא תאימות מפורטים העדכונים הנדרשים.

השיטה שמתוארת למעלה לעדכון קוד Python 2 כדי ליצור סדרות תואמות ל-Python 3 מתייחסת לסדרות לטווח קצר, כמו אלה שמאוחסנות ב-Memcache. יכול להיות שתצטרכו לעדכן או לשכתב סדרות ארוכות של Python 2, כמו אלה שמאוחסנות ב-Datastore כחלק מההעברה. לדוגמה, יכול להיות שיהיה צורך לשדרג סריאליזציה שנכתבה באמצעות google.appengine.ext.ndb.model.PickleProperty.

במדריך התאימות אפשר לקרוא מידע נוסף על מגבלות ועל בעיות פחות נפוצות.

מסגרות אינטרנט

webapp2 לא כלול ב-Python 3 ולא נתמך בו, ולכן צריך לכתוב מחדש כל אפליקציה כדי להשתמש בכל מסגרת תואמת WSGI (כמו Flask).

אסטרטגיית המיגרציה המומלצת היא קודם להחליף את השימוש ב-webapp2 באפליקציית Python 2.7 ב-Flask (או ב-framework חלופי לאינטרנט כמו Django,‏ Pyramid,‏ Bottle או web.py), תוך שמירה על Python 2.7. לאחר מכן, כשהאפליקציה המעודכנת יציבה, מעבירים את הקוד ל-Python 3 ומבצעים פריסה ובדיקה באמצעות App Engine ל-Python 3.

דוגמאות להמרת אפליקציות Python 2.7 שמשתמשות ב-webapp2 לשימוש ב-Flask framework מופיעות במקורות המידע הנוספים האלה.

שימוש באפליקציות ניהול

לאפליקציית Python 3 יכול להיות רק סקריפט אחד שמשויך אליה, ולכן אם בקובץ app.yaml יש כמה פונקציות handler של script שממפות כתובות URL לסקריפטים שונים, תצטרכו לשלב את הסקריפטים האלה לסקריפט אחד שמטפל בניתוב כתובות ה-URL.

בדוגמה הבאה מוצגים ההבדלים בין פונקציות ה-handler בקובץ app.yaml עבור סביבות הריצה המתאימות.

Python 2

runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /
  script: home.app

- url: /index\.html
  script: home.app

- url: /stylesheets
  static_dir: stylesheets

- url: /(.*\.(gif|png|jpg))$
  static_files: static/\1
  upload: static/.*\.(gif|png|jpg)$

- url: /admin/.*
  script: admin.app
  login: admin

- url: /.*
  script: not_found.app

‫Python 3

runtime: python314

app_engine_bundled_services:
- datastore_v3
- memcache
- user
...

#list your bundled services

handlers:
- url: /stylesheets
  static_dir: stylesheets

- url: /(.*\.(gif|png|jpg))$
  static_files: static/\1
  upload: static/.*\.(gif|png|jpg)$

- url: /admin/.*
  script: auto
  login: admin

אפליקציית Python 3 צריכה לטפל בניתוב כתובות URL (לדוגמה, באמצעות מעצבי Flask).

אם רוצים להשתמש בכמה רכיבי handler עם תבניות שונות של כתובות URL, או אם רוצים להשתמש במאפיינים אחרים ברכיבי ה-handler, צריך לציין script: auto בכל רכיב handler.script

אפשר גם לשנות את התנהגות ברירת המחדל של ההפעלה על ידי ציון שדה entrypoint בקובץ app.yaml.

למידע נוסף על אופן השימוש בגורמים מטפלים ספציפיים, אפשר לעיין במאמרים בנושא Blobstore, ‏ Deferred ו-Mail.

Thread safety

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

מידע נוסף זמין במאמר שגיאות אבטחה בשימוש בחבילות שירותים מדור קודם ל-Python.

שימוש בשירות אחזור של כתובות אתרים

כדי להשתמש ב-URL Fetch ל-Python, צריך לקרוא באופן מפורש לספריית URL Fetch.

אם האפליקציה שלכם ב-Python 3 משתמשת ב-URL Fetch API, כותרת הבקשה X-Appengine-Inbound-Appid מתווספת כשהאפליקציה שולחת בקשה לאפליקציה אחרת ב-App Engine. כך האפליקציה המקבלת יכולה לאמת את הזהות של האפליקציה שקוראת לה. מידע נוסף זמין במאמר בנושא העברת בקשות יוצאות.

דוגמה (App Engine ndb)

למטה מופיעה אפליקציית Python 2 בסיסית שרושמת ביקורים בדף באמצעות App Engine ndb כדי לגשת ל-Datastore. האפליקציה המקבילה היא אפליקציית Python 3 שבה webapp2 השימוש הוחלף ב-Flask, והשינויים הנדרשים שמתוארים למעלה כדי לגשת לשירותים בחבילה ב-Python 3 יושמו.

‫Python 2 (webapp2)

import os
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

class MainHandler(webapp2.RequestHandler):
    'main application (GET) handler'
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10)
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

Python 3 (Flask)

from flask import Flask, render_template, request
from google.appengine.api import wrap_wsgi_app
from google.appengine.ext import ndb

app = Flask(__name__)
app.wsgi_app = wrap_wsgi_app(app.wsgi_app)


class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)


@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

אפשר למצוא את שתי האפליקציות האלה במאגר הקוד הפתוח של תוכן ההעברה של Python App Engine (דוגמאות קוד, סרטונים, שיעורי Codelab), באופן ספציפי בתיקיות mod0 ו-mod1b.