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 sont destinées à aider les développeurs Google Cloud sans serveur à moderniser leurs applications en les guidant tout au long d'une ou plusieurs migrations, principalement en les aidant à abandonner les anciens services. Cela rend vos applications plus portables et vous offre plus d'options et de flexibilité, ce qui vous permet de vous intégrer à une plus large gamme de produits Cloud et d'y accéder, et de passer plus facilement aux versions linguistiques 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 large pour inclure d'autres plates-formes sans serveur comme Cloud Functions et Cloud Run, ou ailleurs si nécessaire.

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 la prochaine étape de la migration 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 ndb App Engine 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 le souhaite pas, 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). Maintenant que cette intégration blobstore App Engine est implémentée, nous pouvons la migrer vers Cloud Storage dans le prochain atelier de programmation (module 16).

App Engine donne accès aux systèmes de création de modèles Django et Jinja2. L'une des différences de cet exemple (en plus de l'ajout de l'accès à Blobstore) est qu'il passe 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 conception de modèles par défaut. Nous allons donc commencer à 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, cela signifie qu'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 base 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'une des conditions préalables à cet atelier de programmation est de disposer d'un exemple d'application fonctionnel 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 pour chaque étape jusqu'à obtenir un code similaire à 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 ces étapes effectuées et que vous avez vérifié que votre application Web fonctionne (avec un résultat semblable à celui ci-dessous), vous êtes prêt à ajouter l'utilisation de 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 importante. Toutefois, comme mentionné précédemment, nous passons des modèles Django (par défaut) à Jinja2. Pour effectuer la transition, les utilisateurs doivent donc spécifier la dernière version de Jinja2 disponible sur les serveurs App Engine. Pour ce faire, ils doivent l'ajouter à 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 n'a besoin d'être mis à jour. Passons donc aux fichiers d'application.

5. Modifier les fichiers d'application

Importations et compatibilité avec Jinja2

Le premier ensemble 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. L'objectif du module os est de créer un nom de chemin d'accès à un modèle Django. Étant donné que nous passons à Jinja2, où cette opération est gérée, l'utilisation de os ainsi que du moteur de rendu de modèle Django, google.appengine.ext.webapp.template, n'est plus nécessaire. Nous les supprimons donc.
  2. Importez l'API Blobstore : google.appengine.ext.blobstore
  3. Importez les gestionnaires Blobstore trouvés dans le framework webapp d'origine. Ils ne sont pas disponibles dans webapp2 : google.appengine.ext.webapp.blobstore_handlers
  4. Importer la compatibilité 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 passe-partout pour prendre en charge l'utilisation de Jinja2, comme défini dans la documentation 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 prise en charge de Blobstore

Contrairement aux autres migrations de cette série, où nous conservons la fonctionnalité ou la sortie de l'application exemple identiques (ou presque identiques) sans (trop) modifier l'UX, cet exemple s'éloigne plus radicalement de la norme. Au lieu d'enregistrer immédiatement une nouvelle visite, puis d'afficher les dix dernières, nous mettons à jour l'application pour demander à l'utilisateur un artefact de fichier pour enregistrer sa visite. Les utilisateurs finaux peuvent ensuite importer un fichier correspondant ou sélectionner "Ignorer" pour ne rien importer du tout. 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 récentes.

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

Nous stockons davantage de données, en particulier 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(). Étant donné que ces données supplémentaires sont renvoyées avec tout le reste lors de la requête, fetch_visits() reste le même.

Voici les avant/après de ces modifications 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

Importer des fichiers

La modification la plus importante des fonctionnalités concerne 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. Tout cela fait partie du tableau. Voici les modifications requises pour prendre en charge l'importation de fichiers :

  1. La requête du 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 qui s'affiche pour l'utilisateur. Il s'agit d'un lien hypertexte "Afficher" si un fichier à importer est disponible, ou "Aucun" dans le cas contraire. Ces modifications sont apportées au modèle HTML, avec l'ajout d'un formulaire d'importation (plus d'informations à venir).
  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 celui-ci est compatible), une invite de téléchargement s'affiche dans le cas contraire, 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 y envoyer le 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 un seul itinéraire :

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)

Avec 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 "Afficher le 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)

Ce code que nous venons d'ajouter comporte 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 envoie le fichier au gestionnaire d'importation pour qu'il l'envoie à 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 lui est attribué, son BlobKey.
  • Dans ViewBlobHandler.get, l'appel de blobstore_handlers.BlobstoreDownloadHandler.send avec le 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 visuelle de ce deuxième et dernier ensemble de modifications apportées à main.py :

da2960525ac1b90d.png

Mettre à jour le modèle HTML

Certaines mises à jour de l'application principale affectent l'interface utilisateur de l'application. Des modifications correspondantes sont donc nécessaires 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 de fichier et l'option "Ignorer", respectivement.
  2. Mettez à jour les visites les plus récentes en ajoutant un lien "Afficher" pour les visites avec un fichier importé correspondant, ou "aucune" 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 à apporter : 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. Cette dernière étape vous permet d'apporter toutes les modifications nécessaires pour ajouter l'utilisation de Blobstore à l'exemple d'application du module 0.

(facultatif) "Amélioration" de Cloud Storage

Le stockage Blobstore a fini par évoluer 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, n'est-ce pas ?

Les mises à jour apportées à l'application exemple 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(). Vous devez donc effectuer 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, trier par type de fichier :

f61f7a23a1518705.png

Pour implémenter une fonctionnalité similaire pour les images, par exemple, vous aurez un code comme 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 validerez également les images importées à l'aide d'un outil tel que le module imghdr de la bibliothèque standard Python pour confirmer le type d'image. Enfin, vous souhaiterez probablement limiter la taille des importations en cas de personnes malveillantes.

Imaginons que tout cela ait été fait. Comment mettre à jour notre application pour qu'elle permette de spécifier où stocker les fichiers importés ? La clé consiste à modifier l'appel à 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 où les importations doivent être effectuées, elle ne fait pas partie du fichier main.py dans le dépôt. En revanche, une alternative nommée main-gcs.py est disponible dans le dépôt. Au lieu d'utiliser un "dossier" de bucket distinct, le code de main-gcs.py stocke les importations dans le bucket "racine" (PROJECT_ID.appspot.com) comme main.py, mais fournit la structure dont vous avez besoin si vous deviez dériver l'échantillon 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 et en vérifiant qu'elle fonctionne comme prévu et dans toutes les sorties reflétées. Après la validation de l'application, effectuez les étapes de nettoyage et réfléchissez aux prochaines étapes.

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.png À partir de là, les utilisateurs finaux peuvent 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 visite le plus récent, désormais enrichi de liens "Afficher" ou de la mention "Aucun" entre les codes temporels de visite et les informations sur les visiteurs :

f5ac6b98ee8a34cb.png

Bravo ! Vous avez terminé cet atelier de programmation et ajouté l'utilisation d'App Engine Blobstore à l'application exemple du module 0. Votre code doit maintenant correspondre à celui du dossier FINISH (Module 15). L'autre fichier main-gcs.py est également présent 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 expériences, la plate-forme App Engine dispose d'un quota sans frais. Tant que vous ne dépassez pas ce niveau d'utilisation, aucun frais ne devrait vous être facturé. Cela concerne le calcul, mais des frais peuvent également s'appliquer aux services App Engine concernés. Pour en savoir plus, consultez la page de tarification. 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 de calcul sans serveur Google Cloud comme App Engine entraîne de légers coûts de compilation et de stockage. Cloud Build et Cloud Storage disposent chacun de leur propre quota sans frais. Le stockage de cette image utilise une partie de ce quota. Toutefois, il est possible que vous résidiez dans une région où ce niveau sans frais n'est pas disponible. Veillez donc à surveiller votre utilisation de l'espace de stockage pour minimiser les coûts potentiels. Voici quelques "dossiers" Cloud Storage spécifiques que vous devez 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 poursuivre avec cette application ni avec d'autres ateliers de programmation de migration associés et que vous souhaitez tout supprimer complètement, arrêtez votre projet.

Spécifique à cet atelier de programmation

Les services listés ci-dessous sont propres à 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 l'utilisation de 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 n'avez pas 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 App Engine Blobstore.

Au-delà du module 16, de nombreuses autres migrations sont possibles, comme 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, vous redirige vers tous les ateliers de programmation et toutes les vidéos disponibles, et vous indique également les migrations à envisager et l'ordre de migration à suivre.

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

Le tableau ci-dessous contient des liens vers les dossiers du dépôt pour les modules 0 (START) et 15 (FINISH). Vous pouvez également y accéder depuis le dépôt pour 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

Vous trouverez ci-dessous 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.