Acessar serviços legados para Python 3 reunidos em um pacote

Nesta página, descrevemos como instalar e usar os serviços em pacote legados com o ambiente de execução do Python 3 no ambiente padrão. O aplicativo precisa acessar os serviços incluídos pelo SDK de serviços do App Engine para Python 3.

Antes de começar

Como instalar o SDK de serviços do App Engine

Para instalar o SDK de serviços do App Engine, siga estas etapas:

  1. Inclua o SDK ao app adicionando a seguinte linha ao arquivo requirements.txt:

    appengine-python-standard>=1.0.0
    

    É possível encontrar o SDK no GitHub no repositório appengine-python-standard e em PyPI.

  2. Adicione o seguinte código ao script Python principal: Este código cria um middleware WSGI que define as variáveis necessárias para ativar as chamadas de 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)
    

    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 arquivo app.yaml antes de implantar o aplicativo:

    app_engine_apis: true
    
  4. Para implantar o aplicativo, use o comando gcloud app deploy.

Considerações sobre a migração

Esteja ciente das seguintes considerações se você estiver migrando para o ambiente de execução do Python 3 e seu aplicativo usar pacotes de serviços legados.

Teste

Para testar localmente a funcionalidade de serviços em pacote legados no app em Python 3, use o servidor de desenvolvimento local. Ao executar o comando dev_appserver.py, defina o argumento --runtime_python_path para incluir um caminho para o interpretador do Python 3. Por exemplo:

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

Também é possível 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

Serviços compartilhados, incluindo Memcache, Cloud NDB e deferred, usam o módulo pickle para serializar e compartilhar objetos do Python. Se o ambiente do App Engine usa o Python 2 e o Python 3, o que é comum durante uma migração, garanta que os objetos serializados compartilhados gravados por uma versão do Python possam ser reconstituídos pela outra. Você pode encontrar orientações sobre como implementar a compatibilidade com o Pickle em várias versões no guia.

Por padrão, o Python 3 usa protocolos de conservação que não são compatíveis com o Python 2. Isso pode causar falhas quando o aplicativo tenta reconstituir um objeto Python em um ambiente Python 2 gravado em um ambiente Python 3. Para evitar esse problema, defina as seguintes variáveis de ambiente no arquivo app.yaml do seu aplicativo Python 3 conforme necessário:

  • Para apps que usam o Memcache, incluindo apps que usam o NDB, defina: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • Para aplicativos que usam o NoSQL para se conectar ao Datastore, defina: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • Para apps que usam deferred, defina: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

No Python 2, os objetos string contêm uma sequência de valores de byte de 8 bits. No Python 3, os objetos string contêm uma sequência de caracteres Unicode. Por padrão, o pickle do Python 3 traduz um string do Python 2 em unicode interpretando o string do Python 3 como ASCII. Isso pode levar a erros para valores fora do intervalo de caracteres ASCII de 0 a 127. O Memcache é compatível com a substituição desse mapeamento padrão.

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 em uma string do Python 2. Isso evita erros de decodificação No entanto, se a string do Python 2 contiver dados unicode reais fora do intervalo latin1, como os dados lidos em um arquivo, o cPickle não os mapeará corretamente. Portanto, é importante atualizar o código Python 2 para manter dados unicode com objetos unicode, e não com objetos string, para os objetos escolhidos. O guia de compatibilidade inclui detalhes sobre as atualizações necessárias.

O método descrito anteriormente para atualizar o código do Python 2 para produzir serializações compatíveis com o Python 3 trata de serializações de curta duração, como as armazenadas no Memcache. Talvez seja necessário atualizar ou reescrever serializações de longa duração do Python 2, como as armazenadas no Datastore como parte da migração. Por exemplo, a serialização escrita com google.appengine.ext.ndb.model.PickleProperty pode exigir um upgrade.

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

Frameworks da Web

webapp2 não é incluído ou compatível no Python 3,. Portanto, os aplicativos precisam ser reescritos para usar qualquer framework compatível com WSGI (como Flask).

Uma estratégia de migração recomendada é primeiro substituir o uso do webapp2 no app em Python 2.7 pelo Flask (ou um framework da Web alternativo, como Django, Pyramid, Bottle ou web.py) enquanto permanecerem em Python 2.7. Em seguida, quando o app atualizado estiver estável, migre o código para o Python 3 e implante e teste usando o App Engine para Python 3.

Para exemplos de como converter apps Python 2.7 que usam webapp2 para usar o framework Flask, consulte estes recursos adicionais.

Como usar gerenciadores

Um app em Python 3 pode ter apenas um script associado a ele. Portanto, se o app.yaml tiver vários gerenciadores script que mapeiam URLs para scripts diferentes, será necessário combinar esses scripts em um que processe o roteamento de URL.

O exemplo a seguir mostra as diferenças de gerenciador no arquivo app.yaml para os respectivos ambientes 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: 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

O aplicativo Python 3 precisa gerenciar o roteamento de URL (por exemplo, com decoradores Flask).

Se você quiser usar vários gerenciadores script com padrões de URL diferentes ou se quiser usar outros atributos nos gerenciadores, cada gerenciador precisará especificar script: auto.

Você também pode substituir o comportamento de inicialização padrão especificando um campo entrypoint no arquivo app.yaml.

Consulte as visões gerais de repositório de blobs, Deferred e de Mail para mais informações sobre como usar gerenciadores específicos.

Concorrência segura

Presume-se que os apps são seguros para threads. As chamadas de API precisam ser feitas no thread de solicitação. Se você usa uma API de serviços incluídos legada quando o app está sendo iniciado, isso pode causar erros de segurança.

Para saber mais, consulte Erros de segurança ao usar serviços incluídos legados para Python.

Como usar a busca de URL

Para usar a busca de URL para Python, você precisa chamar explicitamente a biblioteca de busca de URL.

Se o aplicativo Python 3 usar a API URL Fetch, o cabeçalho de solicitação X-Appengine-Inbound-Appid será adicionado quando o aplicativo enviar uma solicitação para outro aplicativo do App Engine. Isso permitirá que o aplicativo receptor verifique a identidade do aplicativo de chamada. Para saber mais, consulte Como migrar solicitações de saída.

Exemplo (App Engine ndb)

Veja abaixo um aplicativo básico do Python 2 que registra visitas à página usando o ndb do App Engine para acessar o Datastore. O complemento dele é um app equivalente do Python 3 em que o uso do webapp2 foi substituído pelo Flask, e as alterações necessárias descritas acima para acessar 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)

Os dois apps podem ser encontrados no repositório de código aberto do conteúdo de migração do App Engine para Python (exemplos de código, vídeos, codelabs), especificamente nas pastas mod0 e mod1b, respectivamente.