Aceda a serviços incluídos antigos para o Python 3

Esta página descreve como instalar e usar serviços agrupados antigos com o tempo de execução do Python 3 para o ambiente padrão. A sua app tem de aceder aos serviços incluídos através do SDK dos serviços do App Engine para Python 3.

Antes de começar

Instalar o SDK dos serviços do App Engine

Para instalar o SDK dos serviços do App Engine, siga estes passos:

  1. Inclua o SDK na sua app adicionando a seguinte linha ao ficheiro requirements.txt:

    appengine-python-standard>=1.0.0
    

    Pode encontrar o SDK no GitHub no repositório appengine-python-standard e no PyPI.

  2. Adicione o seguinte código ao seu script Python principal. Este código cria middleware WSGI que define as variáveis necessárias para ativar as chamadas da API.

    Frasco

    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)
    

    Pirâmide

    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. Adicione a seguinte linha ao ficheiro app.yaml antes de implementar a app:

    app_engine_apis: true
    
  4. Para implementar a sua app, use o comando gcloud app deploy.

Considerações sobre a migração

Deve ter em atenção as seguintes considerações se estiver a migrar para o tempo de execução do Python 3 e a sua app usar serviços agrupados antigos.

Testes

Para testar localmente a funcionalidade dos serviços agrupados antigos na sua app Python 3, use o servidor de desenvolvimento local. Quando executar o comando dev_appserver.py, tem de definir o argumento --runtime_python_path para incluir um caminho para o intérprete do Python 3. Por exemplo:

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

Também pode definir o argumento como uma lista separada por vírgulas de pares [RUNTIME_ID]=[PYTHON_INTERPRETER_PATH]. Por exemplo:

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

Compatibilidade com o Pickle

Os serviços partilhados, incluindo o Memcache, o Cloud NDB e o deferred, usam o módulo pickle para serializar e partilhar objetos Python. Se o seu ambiente do App Engine usar o Python 2 e o Python 3, o que é comum durante uma migração, tem de garantir que os objetos serializados partilhados escritos por uma versão do Python podem ser reconstituídos pela outra. Pode encontrar orientações sobre a implementação da compatibilidade com o pickle entre versões no guia.

Por predefinição, o Python 3 usa protocolos de pickling que não são suportados no Python 2. Isto pode causar falhas quando a sua app tenta reconstituir um objeto Python num ambiente Python 2 que foi escrito num ambiente Python 3. Para evitar este problema, defina as seguintes variáveis de ambiente no ficheiro app.yaml da sua app Python 3, conforme necessário:

  • Para apps que usam a cache de memória, incluindo apps que usam o NDB, defina: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • Para apps que usam o NDB para se ligarem ao Datastore, defina: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • Para apps que usam o carregamento diferido, defina: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

No Python 2, os objetos string contêm uma sequência de valores de bytes de 8 bits. No Python 3, os objetos string contêm uma sequência de carateres Unicode. Por predefinição, o Python 3 pickle converte um Python 2 string em Unicode interpretando o Python 3 string como ASCII. Isto pode originar erros para valores fora do intervalo de carateres ASCII de 0 a 127. O Memcache suporta a substituição deste mapeamento predefinido.

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

A codificação latin1 define um mapeamento para cada um dos 256 valores possíveis de cada byte num string do Python 2. Isto evita erros de descodificação. No entanto, se o seu Python 2 string contiver dados unicode reais fora do intervalo latin1, como dados lidos de um ficheiro, o cPickle não mapeia os dados corretamente. Por conseguinte, é importante que atualize o seu código Python 2 para conter dados Unicode com objetos unicode e não objetos string, para objetos que usa com Pickle. O guia de compatibilidade inclui detalhes sobre as atualizações necessárias.

O método descrito anteriormente para atualizar o código Python 2 de modo a produzir serializações compatíveis com o Python 3 aborda serializações de curta duração, como as armazenadas no Memcache. Pode ter de atualizar ou reescrever as serializações do Python 2 de longa duração, como as armazenadas no Datastore como parte da sua migração. Por exemplo, a serialização escrita com google.appengine.ext.ndb.model.PickleProperty pode exigir uma atualização.

Consulte o guia de compatibilidade para saber mais sobre as limitações e os problemas menos comuns.

Frameworks Web

O webapp2 não está incluído nem é suportado no Python 3, pelo que qualquer aplicação tem de ser reescrita para usar qualquer framework compatível com WSGI (como o Flask).

Uma estratégia de migração recomendada é substituir primeiro a utilização de webapp2 na sua app Python 2.7 pelo Flask (ou uma framework Web alternativa, como Django, Pyramid, Bottle ou web.py), enquanto permanece no Python 2.7. Em seguida, quando a app atualizada estiver estável, migre o código para o Python 3 e implemente e teste com o App Engine para Python 3.

Para ver exemplos de como converter apps Python 2.7 que usam webapp2 para usar a framework Flask, pode consultar estes recursos adicionais.

Usar controladores

Uma app Python 3 só pode ter um script associado. Por isso, se o seu app.yaml tiver vários processadores script que mapeiam URLs para scripts diferentes, tem de combinar esses scripts num só que processe o encaminhamento de URLs.

O exemplo seguinte mostra as diferenças do controlador no ficheiro app.yaml para os respetivos tempos de execução.

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

A sua app Python 3 tem de processar o encaminhamento de URLs (por exemplo, com decoradores Flask).

Se quiser usar vários processadores script com padrões de URL diferentes ou se quiser usar outros atributos nos seus processadores, cada processador tem de especificar script: auto.

Também pode substituir o comportamento de arranque predefinido especificando um campo entrypoint no seu ficheiro app.yaml.

Consulte as descrições gerais do Blobstore, Deferred e Mail para obter mais informações sobre como usar processadores específicos.

Segurança de threads

As apps são consideradas seguras para threads. As chamadas API têm de ser feitas no pedido de processamento. Se usar uma API de serviços agrupados antiga quando a app está a ser iniciada, isto pode originar erros de segurança.

Para saber mais, consulte o artigo Erros de segurança ao usar serviços agrupados antigos para Python.

Usar a obtenção de URLs

Para usar a API URL Fetch para Python, tem de chamar explicitamente a biblioteca URL Fetch.

Se a sua app Python 3 usar a API URL Fetch, o cabeçalho do pedido X-Appengine-Inbound-Appid é adicionado quando a sua app envia um pedido para outra app do App Engine. Isto permite que a app de receção valide a identidade da app de chamada. Para saber mais, consulte o artigo Migrar pedidos de saída.

Exemplo (App Engine ndb)

Segue-se uma app Python 2 básica que regista visitas a páginas através do App Engine ndb para aceder ao Datastore. A respetiva app complementar é equivalente ao Python 3, em que a webapp2 utilização foi substituída pelo Flask e as alterações necessárias descritas acima para aceder aos serviços incluídos no Python 3 foram implementadas.

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)

Pode encontrar ambas as apps no repositório de código aberto para o conteúdo de migração do Python App Engine (exemplos de código, vídeos, codelabs), especificamente nas pastas mod0 e mod1b, respetivamente.