Elaborazione in background con Python

Molte app devono eseguire l'elaborazione in background al di fuori del contesto di una richiesta web. Questo tutorial crea un'app web che consente agli utenti di inserire il testo da tradurre e poi visualizza un elenco delle traduzioni precedenti. La traduzione viene eseguita in un processo in background per evitare di bloccare la richiesta dell'utente.

Il seguente diagramma illustra il processo di richiesta di traduzione.

Diagramma dell'architettura.

Ecco la sequenza di eventi che descrive il funzionamento dell'app tutorial:

  1. Visita la pagina web per visualizzare un elenco delle traduzioni precedenti, archiviate in Firestore.
  2. Richiedi la traduzione di un testo inserendo un modulo HTML.
  3. La richiesta di traduzione viene pubblicata su Pub/Sub.
  4. Viene attivata una funzione Cloud Run iscritta a quell'argomento Pub/Sub.
  5. La funzione Cloud Run utilizza Cloud Translation per tradurre il testo.
  6. La funzione Cloud Run archivia il risultato in Firestore.

Questo tutorial è rivolto a chiunque sia interessato a scoprire di più sull'elaborazione in background con Google Cloud. Non è richiesta alcuna esperienza pregressa con Pub/Sub, Firestore, App Engine o Cloud Run Functions. Tuttavia, per comprendere tutto il codice, è utile avere una certa esperienza con Python, JavaScript e HTML.

Obiettivi

  • Comprendere ed eseguire il deployment di una funzione Cloud Run.
  • Comprendere ed eseguire il deployment di un'app App Engine.
  • Prova l'app.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il calcolatore prezzi.

I nuovi utenti di Google Cloud potrebbero avere diritto a una prova senza costi.

Al termine delle attività descritte in questo documento, puoi evitare l'addebito di ulteriori costi eliminando le risorse che hai creato. Per saperne di più, consulta Esegui la pulizia.

Prima di iniziare

  1. Accedi al tuo account Google Cloud . Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti senza costi per l'esecuzione, il test e il deployment dei workload.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Cloud Run functions, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Firestore, Cloud Run functions, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. Nella console Google Cloud , apri l'app in Cloud Shell.

    Vai a Cloud Shell

    Cloud Shell fornisce l'accesso da riga di comando alle risorse cloud direttamente dal browser. Apri Cloud Shell nel browser e fai clic su Continua per scaricare il codice campione e modificarlo nella directory dell'applicazione.

  9. In Cloud Shell, configura lo strumento gcloud in modo da utilizzare il tuo progetto Google Cloud :
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Informazioni sulla funzione Cloud Run

  • La funzione inizia importando diverse dipendenze come Firestore e Translation.
    import base64
    import hashlib
    import json
    
    from google.cloud import firestore
    from google.cloud import translate_v2 as translate
  • I client globali Firestore e Translation vengono inizializzati in modo da poter essere riutilizzati tra le chiamate di funzione. In questo modo, non devi inizializzare nuovi client per ogni chiamata di funzione, il che rallenterebbe l'esecuzione.
    # Get client objects once to reuse over multiple invocations.
    xlate = translate.Client()
    db = firestore.Client()
  • L'API Translation traduce la stringa nella lingua che hai selezionato.
    def translate_string(from_string, to_language):
        """ Translates a string to a specified language.
    
        from_string - the original string before translation
    
        to_language - the language to translate to, as a two-letter code (e.g.,
            'en' for english, 'de' for german)
    
        Returns the translated string and the code for original language
        """
        result = xlate.translate(from_string, target_language=to_language)
        return result['translatedText'], result['detectedSourceLanguage']
  • La funzione Cloud Run inizia analizzando il messaggio Pub/Sub per ottenere il testo da tradurre e la lingua di destinazione desiderata.

    Poi, la funzione Cloud Run traduce il testo e lo archivia in Firestore, utilizzando una transazione per assicurarsi che non ci siano traduzioni duplicate.

    def document_name(message):
        """ Messages are saved in a Firestore database with document IDs generated
            from the original string and destination language. If the exact same
            translation is requested a second time, the result will overwrite the
            prior result.
    
            message - a dictionary with fields named Language and Original, and
                optionally other fields with any names
    
            Returns a unique name that is an allowed Firestore document ID
        """
        key = '{}/{}'.format(message['Language'], message['Original'])
        hashed = hashlib.sha512(key.encode()).digest()
    
        # Note that document IDs should not contain the '/' character
        name = base64.b64encode(hashed, altchars=b'+-').decode('utf-8')
        return name
    
    
    @firestore.transactional
    def update_database(transaction, message):
        name = document_name(message)
        doc_ref = db.collection('translations').document(document_id=name)
    
        try:
            doc_ref.get(transaction=transaction)
        except firestore.NotFound:
            return  # Don't replace an existing translation
    
        transaction.set(doc_ref, message)
    
    
    def translate_message(event, context):
        """ Process a pubsub message requesting a translation
        """
        message_data = base64.b64decode(event['data']).decode('utf-8')
        message = json.loads(message_data)
    
        from_string = message['Original']
        to_language = message['Language']
    
        to_string, from_language = translate_string(from_string, to_language)
    
        message['Translated'] = to_string
        message['OriginalLanguage'] = from_language
    
        transaction = db.transaction()
        update_database(transaction, message)

Deployment della funzione Cloud Run

  • In Cloud Shell, nella directory function, esegui il deployment della funzione Cloud Run con un trigger Pub/Sub:

    gcloud functions deploy Translate --runtime=python37 \
    --entry-point=translate_message --trigger-topic=translate \
    --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_GOOGLE_CLOUD_PROJECT

    dove YOUR_GOOGLE_CLOUD_PROJECT è l'ID progetto Google Cloud .

Informazioni sull'app

L'app web è composta da due componenti principali:

  • Un server HTTP Python per gestire le richieste web. Il server ha i seguenti due endpoint:
    • /: elenca tutte le traduzioni esistenti e mostra un modulo che gli utenti possono inviare per richiedere nuove traduzioni.
    • /request-translation: gli invii di moduli vengono inviati a questo endpoint, che pubblica la richiesta su Pub/Sub per essere tradotta in modo asincrono.
  • Un modello HTML compilato con le traduzioni esistenti dal server Python.

Il server HTTP

  • Nella directory app, main.py inizia importando le dipendenze, creando un'app Flask, inizializzando i client Firestore e Translation e definendo un elenco di lingue supportate:

    import json
    import os
    
    from flask import Flask, redirect, render_template, request
    from google.cloud import firestore, pubsub
    from markupsafe import escape
    
    
    app = Flask(__name__)
    
    # Get client objects to reuse over multiple invocations
    db = firestore.Client()
    publisher = pubsub.PublisherClient()
    
    # Keep this list of supported languages up to date
    ACCEPTABLE_LANGUAGES = ("de", "en", "es", "fr", "ja", "sw")
  • Il gestore dell'indice (/) recupera tutte le traduzioni esistenti da Firestore e compila un modello HTML con l'elenco:

    @app.route("/", methods=["GET"])
    def index():
        """The home page has a list of prior translations and a form to
        ask for a new translation.
        """
    
        doc_list = []
        docs = db.collection("translations").stream()
        for doc in docs:
            doc_list.append(doc.to_dict())
    
        return render_template("index.html", translations=doc_list)
    
    
  • Le nuove traduzioni vengono richieste inviando un modulo HTML. Il gestore di traduzione delle richieste, registrato all'indirizzo /request-translation, analizza l'invio del modulo, convalida la richiesta e pubblica un messaggio in Pub/Sub:

    @app.route("/request-translation", methods=["POST"])
    def translate():
        """Handle a request to translate a string (form field 'v') to a given
        language (form field 'lang'), by sending a PubSub message to a topic.
        """
        source_string = request.form.get("v", "")
        to_language = escape(request.form.get("lang", ""))
    
        if source_string == "":
            return "Invalid request, you must provide a value.", 400
    
        if to_language not in ACCEPTABLE_LANGUAGES:
            return f"Unsupported language: {to_language}", 400
    
        message = {
            "Original": source_string,
            "Language": to_language,
            "Translated": "",
            "OriginalLanguage": "",
        }
    
        topic_name = (
            f"projects/{os.getenv('GOOGLE_CLOUD_PROJECT')}/topics/translate"
        )
        publisher.publish(
            topic=topic_name, data=json.dumps(message).encode("utf-8")
        )
        return redirect("/")
    
    

Modello HTML

Il modello HTML è la base della pagina HTML mostrata all'utente in modo che possa visualizzare le traduzioni precedenti e richiederne di nuove. Il modello viene compilato dal server HTTP con l'elenco delle traduzioni esistenti.

  • L'elemento <head> del modello HTML include metadati, fogli di stile e JavaScript per la pagina:
    <html>
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Translations</title>
    
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                $("#translate-form").submit(function(e) {
                    e.preventDefault();
                    // Get value, make sure it's not empty.
                    if ($("#v").val() == "") {
                        return;
                    }
                    $.ajax({
                        type: "POST",
                        url: "/request-translation",
                        data: $(this).serialize(),
                        success: function(data) {
                            // Show snackbar.
                            console.log(data);
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--red-100");
                            $("#snackbar").addClass("mdl-color--green-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation requested'
                            });
                        },
                        error: function(data) {
                            // Show snackbar.
                            console.log("Error requesting translation");
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--green-100");
                            $("#snackbar").addClass("mdl-color--red-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation request failed'
                            });
                        }
                    });
                });
            });
        </script>
        <style>
            .lang {
                width: 50px;
            }
            .translate-form {
                display: inline;
            }
        </style>
    </head>

    La pagina recupera gli asset CSS e JavaScript di Material Design Lite (MDL). MDL ti consente di aggiungere l'aspetto di Material Design ai tuoi siti web.

    La pagina utilizza JQuery per attendere il completamento del caricamento del documento e impostare un gestore di invio del modulo. Ogni volta che viene inviato il modulo di richiesta di traduzione, la pagina esegue una convalida minima del modulo per verificare che il valore non sia vuoto, quindi invia una richiesta asincrona all'endpoint /request-translation.

    Infine, viene visualizzata una snackbar MDL per indicare se la richiesta è andata a buon fine o se si è verificato un errore.

  • Il corpo HTML della pagina utilizza un layout MDL e diversi componenti MDL per visualizzare un elenco di traduzioni e un modulo per richiedere traduzioni aggiuntive:
    <body>
        <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
            <header class="mdl-layout__header">
                <div class="mdl-layout__header-row">
                    <!-- Title -->
                    <span class="mdl-layout-title">Translate with Background Processing</span>
                </div>
            </header>
            <main class="mdl-layout__content">
                <div class="page-content">
                    <div class="mdl-grid">
                    <div class="mdl-cell mdl-cell--1-col"></div>
                        <div class="mdl-cell mdl-cell--3-col">
                            <form id="translate-form" class="translate-form">
                                <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                                </div>
                                <select class="mdl-textfield__input lang" name="lang">
                                    <option value="de">de</option>
                                    <option value="en">en</option>
                                    <option value="es">es</option>
                                    <option value="fr">fr</option>
                                    <option value="ja">ja</option>
                                    <option value="sw">sw</option>
                                </select>
                                <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                                    name="submit">Submit</button>
                            </form>
                        </div>
                        <div class="mdl-cell mdl-cell--8-col">
                            <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                                <thead>
                                    <tr>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                                    </tr>
                                </thead>
                                <tbody>
                                {% for translation in translations %}
                                    <tr>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--primary">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ translation['OriginalLanguage'] }} </span>
                                            </span>
                                        {{ translation['Original'] }}
                                        </td>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--accent">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ translation['Language'] }} </span>
                                            </span>
                                            {{ translation['Translated'] }}
                                        </td>
                                    </tr>
                                {% endfor %}
                                </tbody>
                            </table>
                            <br/>
                            <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">
                                Refresh
                            </button>
                        </div>
                    </div>
                </div>
                <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
                    <div class="mdl-snackbar__text mdl-color-text--black"></div>
                    <button type="button" class="mdl-snackbar__action"></button>
                </div>
            </main>
        </div>
    </body>
    
    </html>

Deployment dell'app web

Puoi utilizzare l'ambiente standard di App Engine per creare ed eseguire il deployment di un'app che viene eseguita in modo affidabile anche se sottoposta a un carico elevato e con grandi quantità di dati.

Questo tutorial utilizza l'ambiente standard di App Engine per eseguire il deployment del frontend HTTP.

app.yaml configura l'app App Engine:

runtime: python312
  • Dalla stessa directory del file app.yaml, esegui il deployment dell'app nell'ambiente standard di App Engine:
    gcloud app deploy

Testare l'app

Dopo aver eseguito il deployment della funzione Cloud Run e dell'app App Engine, prova a richiedere una traduzione.

  1. Per visualizzare l'app nel browser,inserisci il seguente URL:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Sostituisci quanto segue:

    C'è una pagina con un elenco vuoto di traduzioni e un modulo per richiedere nuove traduzioni.

  2. Nel campo Testo da tradurre, inserisci un testo da tradurre, ad esempio Hello, World.
  3. Seleziona una lingua dall'elenco a discesa in cui vuoi tradurre il testo.
  4. Fai clic su Invia.
  5. Per aggiornare la pagina, fai clic su Aggiorna . È presente una nuova riga nell'elenco delle traduzioni. Se non vedi una traduzione, attendi qualche altro secondo e riprova. Se ancora non vedi una traduzione, consulta la sezione successiva sul debug dell'app.

Esecuzione del debug dell'app

Se non riesci a connetterti alla tua app App Engine o non vedi nuove traduzioni, controlla quanto segue:

  1. Verifica che i comandi di deployment gcloud siano stati completati correttamente e non abbiano generato errori. Se si sono verificati errori, correggili e prova a eseguire di nuovo il deployment della funzione Cloud Run e dell'app App Engine.
  2. Nella console Google Cloud , vai alla pagina Visualizzatore log.

    Vai alla pagina del visualizzatore log
    1. Nell'elenco a discesa Risorse selezionate di recente, fai clic su GAE Application, quindi fai clic su Tutti i module_id. Viene visualizzato un elenco di richieste relative alle tue visite all'app. Se non vedi un elenco di richieste, verifica di aver selezionato Tutti i module_id dall'elenco a discesa. Se visualizzi messaggi di errore stampati nella console Google Cloud , verifica che il codice dell'app corrisponda al codice nella sezione relativa alla comprensione dell'app.
    2. Nell'elenco a discesa Risorse selezionate di recente, fai clic su funzione Cloud Functions, quindi fai clic su Tutti i nomi delle funzioni. Viene visualizzata una funzione elencata per ogni traduzione richiesta. In caso contrario, verifica che la funzione Cloud Run e l'app App Engine utilizzino lo stesso argomento Pub/Sub:

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

Elimina il progetto Google Cloud

  1. Nella console Google Cloud , vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona quello che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID del progetto e fai clic su Chiudi per eliminare il progetto.

Elimina l'istanza App Engine

  1. Nella console Google Cloud , vai alla pagina Versioni per App Engine.

    Vai a Versioni

  2. Seleziona la casella di controllo per la versione dell'app non predefinita che vuoi eliminare.
  3. Per eliminare la versione dell'app, fai clic su Elimina.

Elimina la funzione Cloud Run

  • Elimina la funzione Cloud Run che hai creato in questo tutorial:
    gcloud functions delete Translate

Passaggi successivi