Utiliser le POP d'App Engine (module 15)

1. Présentation

La série d'ateliers de programmation Serverless Migration Station (tutoriels pratiques à suivre à votre rythme) et les vidéos associées ont pour but d'aider les développeurs sans serveur Google Cloud à moderniser leurs applications en les guidant à travers une ou plusieurs migrations, principalement en abandonnant les anciens services. Cela rend vos applications plus portables et vous offre plus d'options et de flexibilité, ce qui vous permet de les intégrer à un plus grand nombre de produits Cloud et d'y accéder, et de passer plus facilement à des versions de langage plus récentes. Bien qu'elle se concentre initialement sur les premiers utilisateurs de Cloud, principalement les développeurs App Engine (environnement standard), cette série est suffisamment vaste pour inclure d'autres plates-formes sans serveur telles que Cloud Functions et Cloud Run, ou d'autres, le cas échéant.

Cet atelier de programmation du module 15 explique comment ajouter l'utilisation d'App Engine blobstore à l'application exemple du module 0. Vous serez alors prêt à migrer cette utilisation vers Cloud Storage dans le module 16.

Vous apprendrez à

  • Ajouter l'utilisation de l'API/bibliothèque Blobstore d'App Engine
  • Stocker les importations des utilisateurs dans le service blobstore
  • Préparer l'étape suivante pour migrer vers Cloud Storage

Prérequis

Enquête

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Quel est votre niveau d'expérience avec Python ?

Débutant Intermédiaire Expert

Quel est votre niveau d'expérience avec les services Google Cloud ?

Débutant Intermédiaire Expert

2. Arrière-plan

Pour migrer depuis l'API Blobstore d'App Engine, ajoutez son utilisation à l'application App Engine ndb de référence existante du module 0. L'application exemple affiche les dix visites les plus récentes de l'utilisateur. Nous modifions l'application pour inviter l'utilisateur final à importer un artefact (un fichier) correspondant à sa "visite". Si l'utilisateur ne souhaite pas le faire, il peut ignorer cette étape. Quelle que soit la décision de l'utilisateur, la page suivante affiche le même résultat que l'application du module 0 (et de nombreux autres modules de cette série). Une fois cette intégration de blobstore à App Engine implémentée, nous pourrons migrer vers Cloud Storage dans le prochain atelier de programmation (module 16).

App Engine permet d'accéder aux systèmes de modèles Django et Jinja2. L'une des différences de cet exemple (outre l'ajout de l'accès à Blobstore) est qu'il passe de l'utilisation de Django dans le module 0 à Jinja2 dans le module 15. Une étape clé de la modernisation des applications App Engine consiste à migrer les frameworks Web de webapp2 vers Flask. Ce dernier utilise Jinja2 comme système de création de modèles par défaut. Nous commençons donc à nous orienter dans cette direction en implémentant Jinja2 tout en restant sur webapp2 pour l'accès à Blobstore. Comme Flask utilise Jinja2 par défaut, aucune modification du modèle ne sera nécessaire dans le module 16.

3. Configuration/Préparation

Avant de passer à la partie principale du tutoriel, configurez votre projet, obtenez le code et déployez l'application de référence pour commencer avec un code fonctionnel.

1. Configurer le projet

Si vous avez déjà déployé l'application du module 0, nous vous recommandons de réutiliser le même projet (et le même code). Vous pouvez également créer un projet ou réutiliser un autre projet existant. Assurez-vous que le projet dispose d'un compte de facturation actif et qu'App Engine est activé.

2. Obtenir un exemple d'application de référence

L'un des prérequis de cet atelier de programmation est de disposer d'une application exemple fonctionnelle du module 0. Si vous n'en avez pas, vous pouvez l'obtenir à partir du dossier "START" du module 0 (lien ci-dessous). Cet atelier de programmation vous guide à travers chaque étape, pour aboutir à un code semblable à celui du dossier "FINISH" du module 15.

Le répertoire des fichiers de départ du module 0 doit se présenter comme suit :

$ ls
README.md               index.html
app.yaml                main.py

3. (Re)Déployer l'application de référence

Étapes préliminaires restantes :

  1. Familiarisez-vous avec l'outil de ligne de commande gcloud
  2. Redéployez l'exemple d'application avec gcloud app deploy
  3. Vérifier que l'application s'exécute sans problème sur App Engine

Une fois que vous avez suivi ces étapes et que votre application Web fonctionne (avec un résultat semblable à celui ci-dessous), vous pouvez ajouter la mise en cache à votre application.

a7a9d2b80d706a2b.png

4. Mettre à jour les fichiers de configuration

app.yaml

La configuration de l'application ne subit aucune modification substantielle. Toutefois, comme indiqué précédemment, nous passons du moteur de création de modèles Django (par défaut) à Jinja2. Pour effectuer ce changement, les utilisateurs doivent spécifier la dernière version de Jinja2 disponible sur les serveurs App Engine, en l'ajoutant à la section des bibliothèques tierces intégrées de app.yaml.

AVANT :

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

Modifiez votre fichier app.yaml en ajoutant une section libraries comme suit :

APRÈS :

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

Aucun autre fichier de configuration ne doit être mis à jour. Passons donc aux fichiers d'application.

5. Modifier les fichiers d'application

Importations et compatibilité avec Jinja2

La première série de modifications pour main.py inclut l'ajout de l'utilisation de l'API Blobstore et le remplacement des modèles Django par Jinja2. Voici ce qui va changer :

  1. Le module os permet de créer un chemin d'accès à un modèle Django. Comme nous passons à Jinja2, qui gère cela, l'utilisation de os et du moteur de rendu de modèles Django, google.appengine.ext.webapp.template, n'est plus nécessaire. Ils sont donc supprimés.
  2. Importez l'API Blobstore : google.appengine.ext.blobstore.
  3. Importez les gestionnaires Blobstore du framework webapp d'origine, car ils ne sont pas disponibles dans webapp2 : google.appengine.ext.webapp.blobstore_handlers
  4. Importez la compatibilité avec Jinja2 à partir du package webapp2_extras.

AVANT :

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

Implémentez les modifications de la liste ci-dessus en remplaçant la section d'importation actuelle dans main.py par l'extrait de code ci-dessous.

APRÈS :

import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers

Après les importations, ajoutez du code récurrent pour permettre l'utilisation de Jinja2, comme défini dans la documentation de webapp2_extras. L'extrait de code suivant encapsule la classe de gestionnaire de requêtes webapp2 standard avec la fonctionnalité Jinja2. Ajoutez donc ce bloc de code à main.py juste après les importations :

class BaseHandler(webapp2.RequestHandler):
    'Derived request handler mixing-in Jinja2 support'
    @webapp2.cached_property
    def jinja2(self):
        return jinja2.get_jinja2(app=self.app)

    def render_response(self, _template, **context):
        self.response.write(self.jinja2.render_template(_template, **context))

Ajouter la compatibilité avec Blobstore

Contrairement aux autres migrations de cette série, où nous conservons les fonctionnalités ou la sortie de l'application exemple identiques (ou presque) sans (trop) modifier l'expérience utilisateur, cet exemple s'écarte plus radicalement de la norme. Plutôt que d'enregistrer immédiatement une nouvelle visite, puis d'afficher les dix plus récentes, nous allons modifier l'application pour demander à l'utilisateur un artefact de fichier avec lequel enregistrer sa visite. Les utilisateurs finaux peuvent ensuite importer un fichier correspondant ou sélectionner "Ignorer" pour ne rien importer. Une fois cette étape terminée, la page "Visites les plus récentes" s'affiche.

Cette modification permet à notre application d'utiliser le service Blobstore pour stocker (et éventuellement afficher ultérieurement) cette image ou un autre type de fichier sur la page des visites les plus récentes.

Mettre à jour le modèle de données et l'implémenter

Nous stockons davantage de données, en mettant à jour le modèle de données pour stocker l'ID (appelé BlobKey) du fichier importé dans Blobstore et en ajoutant une référence pour l'enregistrer dans store_visit(). Comme ces données supplémentaires sont renvoyées avec tout le reste lors de la requête, fetch_visits() reste inchangé.

Voici un avant/après de ces mises à jour avec file_blob, un ndb.BlobKeyProperty :

AVANT :

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)

APRÈS :

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

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

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

Voici une représentation visuelle des modifications apportées jusqu'à présent :

2270783776759f7f.png

Accepter l'importation de fichiers

Le changement de fonctionnalité le plus important est la prise en charge de l'importation de fichiers, qu'il s'agisse d'inviter l'utilisateur à importer un fichier, de prendre en charge la fonctionnalité "Ignorer" ou d'afficher un fichier correspondant à une visite. Tous ces éléments font partie du tableau. Voici les modifications requises pour prendre en charge l'importation de fichiers :

  1. Le gestionnaire principal GET ne récupère plus les visites les plus récentes pour les afficher. Au lieu de cela, il invite l'utilisateur à importer un fichier.
  2. Lorsqu'un utilisateur final envoie un fichier à importer ou ignore ce processus, un POST du formulaire transmet le contrôle au nouveau UploadHandler, dérivé de google.appengine.ext.webapp.blobstore_handlers.BlobstoreUploadHandler.
  3. La méthode POST de UploadHandler effectue l'importation, appelle store_visit() pour enregistrer la visite et déclenche une redirection HTTP 307 pour renvoyer l'utilisateur vers "/", où…
  4. La méthode POST du gestionnaire principal interroge (via fetch_visits()) et affiche les visites les plus récentes. Si l'utilisateur sélectionne "Ignorer", aucun fichier n'est importé, mais la visite est tout de même enregistrée, suivie de la même redirection.
  5. L'affichage des visites les plus récentes inclut un nouveau champ présenté à l'utilisateur : un lien hypertexte "Afficher" si un fichier d'importation est disponible, ou "Aucun" dans le cas contraire. Ces modifications sont implémentées dans le modèle HTML, ainsi que l'ajout d'un formulaire d'importation (bientôt disponible).
  6. Si un utilisateur final clique sur le lien "Afficher" pour une visite avec une vidéo importée, une requête GET est envoyée à un nouveau ViewBlobHandler, dérivé de google.appengine.ext.webapp.blobstore_handlers.BlobstoreDownloadHandler. Le fichier est alors affiché s'il s'agit d'une image (dans le navigateur, si possible), l'utilisateur est invité à le télécharger si ce n'est pas le cas, ou une erreur HTTP 404 est renvoyée si le fichier est introuvable.
  7. En plus de la nouvelle paire de classes de gestionnaires et de la nouvelle paire de routes pour leur envoyer du trafic, le gestionnaire principal a besoin d'une nouvelle méthode POST pour recevoir la redirection 307 décrite ci-dessus.

Avant ces mises à jour, l'application du module 0 ne comportait qu'un gestionnaire principal avec une méthode GET et une seule route :

AVANT :

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)

Une fois ces mises à jour implémentées, il existe désormais trois gestionnaires : 1) un gestionnaire d'importation avec une méthode POST, 2) un gestionnaire de téléchargement "view blob" avec une méthode GET et 3) le gestionnaire principal avec les méthodes GET et POST. Apportez ces modifications pour que le reste de votre application ressemble à ce qui suit.

APRÈS :

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)

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)

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)

Le code que nous venons d'ajouter contient plusieurs appels clés :

  • Dans MainHandler.get, il y a un appel à blobstore.create_upload_url. Cet appel génère l'URL vers laquelle le formulaire POST, en appelant le gestionnaire d'importation pour envoyer le fichier à Blobstore.
  • Dans UploadHandler.post, il y a un appel à blobstore_handlers.BlobstoreUploadHandler.get_uploads. C'est là que la magie opère : le fichier est placé dans Blobstore, et un ID unique et persistant est renvoyé pour ce fichier, sa BlobKey.
  • Dans ViewBlobHandler.get, l'appel de blobstore_handlers.BlobstoreDownloadHandler.send avec l'BlobKey d'un fichier entraîne la récupération du fichier et son transfert vers le navigateur de l'utilisateur final.

Ces appels représentent la majeure partie de l'accès aux fonctionnalités ajoutées à l'application. Voici une représentation graphique de ce deuxième et dernier ensemble de modifications apportées à main.py :

da2960525ac1b90d.png

Mettre à jour le modèle HTML

Certaines des mises à jour de l'application principale affectent l'interface utilisateur (UI) de l'application. Par conséquent, des modifications correspondantes sont requises dans le modèle Web, deux en fait :

  1. Un formulaire d'importation de fichier est requis avec trois éléments d'entrée : un fichier et une paire de boutons d'envoi pour l'importation et l'omission du fichier, respectivement.
  2. Mettez à jour la sortie des visites les plus récentes en ajoutant un lien "view" pour les visites avec une importation de fichier correspondante ou "none" dans le cas contraire.

AVANT :

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

Appliquez les modifications de la liste ci-dessus pour créer le modèle mis à jour :

APRÈS :

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
{% if upload_url %}

<h3>Welcome... upload a file? (optional)</h3>
<form action="{{ upload_url }}" method="POST" enctype="multipart/form-data">
    <input type="file" name="file"><p></p>
    <input type="submit"> <input type="submit" value="Skip">
</form>

{% else %}

<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }}
    <i><code>
    {% if visit.file_blob %}
        (<a href="/view/{{ visit.file_blob }}" target="_blank">view</a>)
    {% else %}
        (none)
    {% endif %}
    </code></i>
    from {{ visit.visitor }}
</li>
{% endfor %}
</ul>

{% endif %}

</body>
</html>

Cette image illustre les mises à jour requises pour index.html :

8583e975f25aa9e7.png

Une dernière modification : Jinja2 préfère que ses modèles se trouvent dans un dossier templates. Créez donc ce dossier et déplacez-y index.html. Avec cette dernière étape, vous avez terminé toutes les modifications nécessaires pour ajouter l'utilisation de Blobstore à l'application exemple du module 0.

"Amélioration" de Cloud Storage (facultatif)

Le stockage Blobstore a ensuite évolué pour devenir Cloud Storage. Cela signifie que les importations Blobstore sont visibles dans la console Cloud, plus précisément dans le navigateur Cloud Storage. La question est de savoir où. La réponse est le bucket Cloud Storage par défaut de votre application App Engine. Son nom correspond au nom de domaine complet de votre application App Engine, PROJECT_ID.appspot.com. C'est très pratique, car tous les ID de projet sont uniques.

Les modifications apportées à l'exemple d'application déposent les fichiers importés dans ce bucket, mais les développeurs ont la possibilité de choisir un emplacement plus spécifique. Le bucket par défaut est accessible par programmation via google.appengine.api.app_identity.get_default_gcs_bucket_name(), ce qui nécessite une nouvelle importation si vous souhaitez accéder à cette valeur, par exemple pour l'utiliser comme préfixe pour organiser les fichiers importés. Par exemple, tri par type de fichier :

f61f7a23a1518705.png

Pour implémenter une telle fonctionnalité pour les images, par exemple, vous devez utiliser un code semblable à celui-ci, ainsi qu'un code qui vérifie les types de fichiers pour choisir le nom de bucket souhaité :

ROOT_BUCKET = app_identity.get_default_gcs_bucket_name()
IMAGE_BUCKET = '%s/%s' % (ROOT_BUCKET, 'images')

Vous allez également valider les images importées à l'aide d'un outil tel que le module imghdr de la bibliothèque Python standard pour confirmer le type d'image. Enfin, vous souhaiterez probablement limiter la taille des importations pour vous protéger des acteurs malveillants.

Imaginons que tout cela ait été fait. Comment pouvons-nous mettre à jour notre application pour permettre de spécifier l'emplacement de stockage des fichiers importés ? L'essentiel est de modifier l'appel de blobstore.create_upload_url dans MainHandler.get pour spécifier l'emplacement souhaité dans Cloud Storage pour l'importation en ajoutant le paramètre gs_bucket_name comme suit :

blobstore.create_upload_url('/upload', gs_bucket_name=IMAGE_BUCKET))

Comme il s'agit d'une mise à jour facultative si vous souhaitez spécifier l'emplacement des importations, elle ne fait pas partie du fichier main.py du dépôt. À la place, une alternative nommée main-gcs.py est disponible et vous pouvez la consulter dans le dépôt. Plutôt que d'utiliser un "dossier" de bucket distinct, le code de main-gcs.py stocke les importations dans le bucket "racine" (PROJECT_ID.appspot.com), tout comme main.py, mais fournit l'échafaudage dont vous avez besoin si vous deviez dériver l'exemple en quelque chose de plus, comme indiqué dans cette section. Vous trouverez ci-dessous une illustration des différences entre main.py et main-gcs.py.

256e1ea68241a501.png

6. Résumé/Nettoyage

Cette section conclut cet atelier de programmation en déployant l'application, en vérifiant qu'elle fonctionne comme prévu et en examinant les résultats. Après la validation de l'application, effectuez les étapes de nettoyage et réfléchissez aux étapes suivantes.

Déployer et vérifier l'application

Redéployez votre application avec gcloud app deploy et vérifiez qu'elle fonctionne comme prévu, avec une expérience utilisateur (UX) différente de celle de l'application du module 0. Votre application comporte désormais deux écrans différents, le premier étant l'invite du formulaire d'importation de fichiers de visite :

f5b5f9f19d8ae978.pngLes utilisateurs finaux peuvent alors importer un fichier et cliquer sur "Envoyer", ou cliquer sur "Ignorer" pour ne rien importer. Dans les deux cas, le résultat est l'écran de la visite la plus récente, désormais complété par des liens "view" (afficher) ou "none" (aucun) entre les codes temporels de la visite et les informations sur le visiteur :

f5ac6b98ee8a34cb.png

Félicitations ! Vous avez terminé cet atelier de programmation en ajoutant l'utilisation du Blobstore App Engine à l'application exemple du module 0. Votre code doit maintenant correspondre à celui du dossier FINISH (module 15). L'alternative main-gcs.py est également présente dans ce dossier.

Effectuer un nettoyage

Général

Si vous avez terminé pour le moment, nous vous recommandons de désactiver votre application App Engine pour éviter d'être facturé. Toutefois, si vous souhaitez effectuer d'autres tests ou essais, la plate-forme App Engine dispose d'un quota sans frais. Tant que vous ne dépassez pas ce niveau d'utilisation, aucuns frais ne devraient vous être facturés. Cela concerne le calcul, mais des frais peuvent également s'appliquer pour les services App Engine concernés. Pour en savoir plus, consultez la page des tarifs. Si cette migration implique d'autres services Cloud, ceux-ci sont facturés séparément. Dans les deux cas, le cas échéant, consultez la section "Spécifique à cet atelier de programmation" ci-dessous.

Pour être tout à fait transparent, le déploiement sur une plate-forme d'informatique sans serveur Google Cloud comme App Engine entraîne des coûts de compilation et de stockage mineurs. Cloud Build dispose de son propre quota sans frais, tout comme Cloud Storage. Le stockage de cette image utilise une partie de ce quota. Cependant, il est possible que vous viviez dans une région qui ne propose pas de niveau sans frais. Surveillez donc votre utilisation de l'espace de stockage pour minimiser les coûts potentiels. Voici quelques "dossiers" Cloud Storage spécifiques que vous devriez examiner :

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • Les liens de stockage ci-dessus dépendent de votre PROJECT_ID et de votre *LOC*ation, par exemple "us" si votre application est hébergée aux États-Unis.

En revanche, si vous ne souhaitez pas continuer à utiliser cette application ou d'autres ateliers de programmation de migration associés et que vous souhaitez tout supprimer, arrêtez votre projet.

Spécifique à cet atelier de programmation

Les services listés ci-dessous sont spécifiques à cet atelier de programmation. Pour en savoir plus, consultez la documentation de chaque produit :

Étapes suivantes

La prochaine migration logique à envisager est abordée dans le module 16, qui explique aux développeurs comment migrer du service Blobstore d'App Engine vers la bibliothèque cliente Cloud Storage. La mise à niveau vous permet d'accéder à davantage de fonctionnalités Cloud Storage et de vous familiariser avec une bibliothèque cliente qui fonctionne pour les applications en dehors d'App Engine, que ce soit dans Google Cloud, dans d'autres clouds ou même sur site. Si vous pensez ne pas avoir besoin de toutes les fonctionnalités disponibles dans Cloud Storage ou si vous vous inquiétez de leur impact sur les coûts, vous pouvez continuer à utiliser Blobstore d'App Engine.

Au-delà du module 16, il existe de nombreuses autres migrations possibles, telles que Cloud NDB et Cloud Datastore, Cloud Tasks ou Cloud Memorystore. Il existe également des migrations interproduits vers Cloud Run et Cloud Functions. Le dépôt de migration contient tous les exemples de code, des liens vers tous les ateliers de programmation et vidéos disponibles, ainsi que des conseils sur les migrations à envisager et l'ordre de migration approprié.

7. Ressources supplémentaires

Problèmes/commentaires concernant l'atelier de programmation

Si vous rencontrez des problèmes avec cet atelier de programmation, commencez par faire une recherche avant de les signaler. Liens vers la recherche et la création d'un signalement :

Ressources de migration

Vous trouverez les liens vers les dossiers du dépôt pour le module 0 (START) et le module 15 (FINISH) dans le tableau ci-dessous. Vous pouvez également y accéder à partir du dépôt de toutes les migrations d'ateliers de programmation App Engine, que vous pouvez cloner ou télécharger sous forme de fichier ZIP.

Atelier de programmation

Python 2

Python 3

Module 0

code

N/A

Module 15 (cet atelier de programmation)

code

N/A

Ressources en ligne

Voici des ressources en ligne qui peuvent être utiles pour ce tutoriel :

App Engine

Google Cloud

Python

Vidéos

Licence

Ce document est publié sous une licence Creative Commons Attribution 2.0 Generic.