Accéder aux anciens services groupés pour Python 3

Cette page explique comment installer et utiliser les anciens services groupés fournis avec l'environnement d'exécution Python 3 pour l'environnement standard. Votre application doit accéder aux services groupés via le SDK des services App Engine pour Python 3.

Avant de commencer

Installer le SDK des services App Engine

Pour installer le SDK des services App Engine, procédez comme suit :

  1. Ajoutez le SDK à votre application en incluant la ligne suivante dans le fichier requirements.txt :

    appengine-python-standard>=1.0.0
    

    Vous trouverez le SDK sur GitHub sous le dépôt appengine-python-standard et sur PyPI.

  2. Ajoutez le code suivant dans votre script Python principal. Ce code crée un middleware WSGI qui définit les variables requises pour activer les appels d'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)
    

    Pyramide

    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. Ajoutez la ligne suivante au fichier app.yaml avant de déployer l'application :

    app_engine_apis: true
    
  4. Pour déployer votre application, utilisez la commande gcloud app deploy.

Considérations sur la migration

Tenez compte des considérations suivantes si vous effectuez une migration vers l'environnement d'exécution Python 3 et que votre application utilise des anciens services groupés.

Tests

Pour tester localement la fonctionnalité concernant les anciens services groupés dans votre application Python 3, utilisez le serveur de développement local. Lorsque vous exécutez la commande dev_appserver.py, vous devez définir l'argument --runtime_python_path en spécifiant un chemin d'accès à l'interpréteur Python 3. Exemple :

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

Vous pouvez également définir l'argument sur une liste de paires [RUNTIME_ID]=[PYTHON_INTERPRETER_PATH] séparées par une virgule. Exemple :

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

Compatibilité avec le module pickle

Les services partagés, y compris Memcache, Cloud NDB et la configuration différée, utilisent le module pickle pour sérialiser et partager des objets Python. Si votre environnement App Engine utilise à la fois Python 2 et Python 3, ce qui est courant lors d'une migration, vous devez vous assurer que les objets sérialisés partagés écrits par une version de Python peuvent être reconstitués par l'autre. Vous trouverez des conseils sur la mise en œuvre de la compatibilité entre versions du module pickle dans ce guide.

Par défaut, Python 3 utilise des protocoles de pickling qui ne sont pas compatibles avec Python 2. Cela peut entraîner des échecs lorsque votre application tente de reconstituer un objet Python dans un environnement Python 2 qui a été écrit dans un environnement Python 3. Pour éviter ce problème, définissez les variables d'environnement suivantes dans le fichier app.yaml de votre application Python 3 si nécessaire :

  • Pour les applications qui utilisent Memcache, y compris celles qui utilisent NDB, définissez : MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • Pour les applications qui utilisent NDB pour se connecter à Datastore, définissez : NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • Pour les applications qui utilisent la configuration différée, définissez : DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

Dans Python 2, les objets string contiennent une séquence de valeurs de 8 octets. Dans Python 3, les objets string contiennent une séquence de caractères Unicode. Par défaut, le module pickle Python 3 traduit un fichier string Python 2 au format Unicode en interprétant l'objet string Python 3 en caractères ASCII. Cela peut entraîner des erreurs pour des valeurs non comprises dans la plage de caractères ASCII de 0 à 127. Memcache permet de remplacer ce mappage par défaut.

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))

L'encodage latin1 définit un mappage pour chacune des 256 valeurs possibles de chaque octet dans un objet string Python 2. Cela évite les erreurs de décodage. Cependant, si votre objet string Python 2 contient des données Unicode réelles en dehors de la plage latin1, telles que des données lues à partir d'un fichier, cPickle ne mappera pas correctement les données. Par conséquent, il est important de mettre à jour votre code Python 2 pour conserver les données Unicode avec des objets unicode et non des objets string, pour les objets que vous "picklez". Le guide de compatibilité inclut des informations sur les mises à jour nécessaires.

La méthode décrite précédemment pour mettre à jour votre code Python 2 afin de produire des sérialisations compatibles avec Python 3 concerne les sérialisations de courte durée, telles que celles stockées dans Memcache. Vous devrez peut-être mettre à jour ou réécrire les sérialisations Python 2 de longue durée, telles que celles stockées dans Datastore lors de votre migration. Par exemple, la sérialisation écrite à l'aide de google.appengine.ext.ndb.model.PickleProperty peut nécessiter une mise à niveau.

Consultez le guide de compatibilité pour en savoir plus sur les limites et les problèmes moins courants.

Frameworks Web

webapp2 n'est pas groupé ou accepté dans Python 3. Les applications doivent donc être réécrites afin d'inclure l'utilisation d'un framework compatible avec WSGI (tel que Flask).

Une stratégie de migration recommandée consiste à remplacer webapp2 dans votre application Python 2.7 par Flask (ou un framework Web alternatif tel que Django, Pyramid, Bottle ou web.py), tout en restant sur Python 2.7. Ensuite, lorsque votre application mise à jour est stable, migrez le code vers Python 3, puis déployez et testez votre application à l'aide d'App Engine pour Python 3.

Pour obtenir des exemples de conversion d'applications Python 2.7 qui définissent webapp2 pour utiliser le framework Flask, vous pouvez consulter ces ressources supplémentaires.

Utiliser des gestionnaires

Une application Python 3 ne peut être associée qu'à un seul script. Par conséquent, si votre fichier app.yaml contient plusieurs gestionnaires script qui mappent des URL à différents scripts, vous devez combiner ces scripts en un seul script qui gère le routage d'URL.

L'exemple suivant montre les différences des gestionnaires dans le fichier app.yaml pour les différents environnements d'exécution.

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: python312
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

Votre application Python 3 doit gérer le routage d'URL (par exemple, avec les décorateurs Flask).

Si vous souhaitez utiliser plusieurs gestionnaires script avec différents formats d'URL ou d'autres attributs dans vos gestionnaires, chaque gestionnaire doit spécifier script: auto.

Vous pouvez également remplacer le comportement de démarrage par défaut en spécifiant un champ entrypoint dans votre fichier app.yaml.

Pour en savoir plus sur l'utilisation de gestionnaires spécifiques, consultez les présentations de Blobstore, du déploiement différé et de la messagerie.

Thread safety

Les applications sont considérées comme sécurisées. Les appels d'API doivent être effectués sur le thread de requête. Le fait d'utiliser une ancienne API de services groupés au démarrage de l'application peut entraîner des erreurs de sécurité.

Pour en savoir plus, consultez la page Erreurs de sécurité lors de l'utilisation des anciens services groupés pour Python.

Utiliser le service URL Fetch

Pour utiliser URL Fetch pour Python, vous devez appeler explicitement la bibliothèque URL Fetch.

Si votre application Python 3 utilise l'API URL Fetch, l'en-tête de requête X-Appengine-Inbound-Appid est ajouté lorsque votre application envoie une requête à une autre application App Engine. Cela permet à l'application de réception de vérifier l'identité de l'application appelante. Pour en savoir plus, consultez la section Migrer des requêtes sortantes.

Exemple (ndb App Engine)

Vous trouverez ci-dessous une application Python 2 de base qui enregistre les visites de pages avec App Engine ndb pour accéder à Datastore. Elle est associée à une application Python 3 équivalente à l'utilisation de webapp2, qui a été remplacée par Flask et les modifications requises décrites ci-dessus pour accéder aux services groupés dans Python 3 ont été mises en œuvre.

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)

Ces deux applications sont disponibles dans le dépôt Open Source du contenu de migration App Engine Python (exemples de code, vidéos, ateliers de programmation), en particulier dans les dossiers mod0 et mod1b, respectivement.