Acabo de resolver um problema que vem me irritando muito nos últimos meses: sincronizar uma quande quantidade de dados com o appengine.
Depois de passar muita raiva com o bulkloader, que inevitavelmente acaba gerando esses Datastore Timeout, decidi fazer um outro esquema que funcionou muito melhor. Um dia coloco aqui, mas não agora.
O lance é que pra enviar os dados, habilitei o https e também autenticação. Assim, os blocos de dados podem ser sincronizados com segurança, apenas por um administrador da aplicação.
O problema foi fazer meu script se autenticar no Google Accounts. Apesar da API de autenticação do appengine, preferi não usá-la pois não há necessidade no meu caso, e optei por usar o formulário genérico do Google.
Encontrei este documento com alguns exemplos e dúvidas, mas não foi suficiente. Finalmente, entendi como o negócio funciona:
- Faz um POST no serviço do Google Accounts enviando usuário, senha, nome de identificação e URL do app;
- O Google Accounts irá fornecer um token, que então deve ser passado pro sistema de autenticação do próprio app (igual ao dev_appserver.py)
- Nesse último request, o Google fornece um Cookie chamado ACSID, que deverá ser usado em todos as próximas requisições.
E pra resolver esse problema, escrevi uma classe chamada GoogleAuth, que recebe os dados necessários, se autentica no Google Accounts, e passa a fornecer o ACSID como string. Em caso de falha, tipo usuário ou senha errada, a classe gera um ValueError.
#!/usr/bin/env python
# coding: utf-8
import sys, urllib, urllib2
class GoogleAuth:
cookie = ''
# save the ACSID cookie before redirecting
class RedirectHandler(urllib2.HTTPRedirectHandler):
def __init__(self, klass): self.klass = klass
def http_error_302(self, req, fp, code, msg, headers):
try: self.klass.cookie = headers.get('Set-Cookie').split(';')[0]
except: pass
return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
# authenticate
def __init__(self, appurl, appname, username, password):
googauth = 'https://www.google.com/accounts/ClientLogin'
# prepare the request data
request_data = dict(
Email=username,
Passwd=password,
source=appname,
service='ah',
accountType='HOSTED_OR_GOOGLE',
)
# get the token from google auth
fd = urllib2.urlopen(googauth, urllib.urlencode(request_data))
auth_dict = dict(x.split('=') for x in fd.read().split('\n') if x)
fd.close()
token = auth_dict.get('Auth')
if auth_dict.get('Error') or not token:
raise ValueError('authentication failed.')
# get the ACSID cookie for further athenticated requests
opener = urllib2.build_opener(self.RedirectHandler(self))
fd = opener.open(appurl+'_ah/login?'+urllib.urlencode({'continue':appurl, 'auth':token}))
fd.close()
# return the cookie
def __str__(self):
return self.cookie
if __name__ == '__main__':
appurl = 'http://myapp.appspot.com/'
appname = 'My App'
username = 'foo'
password = 'bar'
# authenticate
try: acsid = GoogleAuth(appurl, appname, username, password)
except Exception, e:
print str(e)
sys.exit(1)
# now access resources that require authentication...
print 'authentication cookie:', acsid
request = urllib2.Request(appurl, headers={'Cookie':acsid})
chunk = urllib2.urlopen(request).read()

boa cabelo, gostei da limpeza :D