גישה לשירותים מדור קודם בחבילה ל-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.

    Flask

    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_apis: true
    
  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 הערכים האפשריים של כל בייט ב-Python 2 string. כך נמנעות שגיאות בפענוח. עם זאת, אם קובץ Python 2 string מכיל נתוני Unicode בפועל מחוץ לטווח latin1, כמו נתונים שנקראו מקובץ, המיפוי של הנתונים על ידי cPickle לא יהיה נכון. לכן, חשוב לעדכן את הקוד ב-Python 2 כך שיכיל נתוני Unicode עם אובייקטים מסוג unicode ולא אובייקטים מסוג string, עבור אובייקטים שאתם מבצעים עליהם Pickle. במדריך התאימות מפורטים העדכונים הנדרשים.

השיטה שמתוארת למעלה לעדכון קוד Python 2 כדי ליצור סדרות תואמות של Python 3 מתייחסת לסדרות לטווח קצר, כמו אלה שמאוחסנות ב-Memcache. יכול להיות שתצטרכו לעדכן או לכתוב מחדש סריאליזציות של Python 2 לטווח ארוך, כמו אלה שמאוחסנות במאגר נתונים כחלק מההעברה. לדוגמה, יכול להיות שיהיה צורך לשדרג סריאליזציה שנכתבה באמצעות 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_apis: true

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, כל רכיב handler צריך לציין את script: auto.script

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

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

Thread safety

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

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

שימוש ב-URL Fetch

כדי להשתמש ב-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 (דוגמאות קוד, סרטונים, סדנאות קוד), באופן ספציפי בתיקיות mod0 ו-mod1b.