У меня есть приложение, которое выполняет следующую задачу.
- Аутентифицировать пользователя по его электронной почте и паролю.
- Сохраните загруженный файл в BlobStore.
- Проверьте информацию о пользователе из хранилища данных, чтобы увидеть, существует ли старый двоичный объект, связанный с этим пользователем. Если да, удалите старый BLOB-объект из BlobStore.
- Обновить DataStore, чтобы связать новый BLOB-объект в BlobStore с этим пользователем.
Я пытаюсь выполнить шаги 2, 3, 4 внутри транзакции.
db.run_in_transaction(self.upload, email, checksum, version, content)
Однако, как и ожидалось, поскольку я обращаюсь к более чем 1 сущности, я получаю следующую ошибку.
BadRequestError: can't operate on multiple entity groups in a single transaction.
Я не совсем счастлив. Как, в чем польза транзакции, если она не может выполнить элементарную операцию над несколькими таблицами (сущностью)?
Я вынужден использовать базу данных высокой репликации. (что будет стоить мне, с точки зрения выставления счетов)
db.run_in_transaction_options(xg_on, self.upload, email, checksum, version, content)
Опять я получаю следующую ошибку:
BadRequestError: Only ancestor queries are allowed inside transactions.
Это происходит в режиме онлайн:
blob_key = files.blobstore.get_blob_key(file_name)
Мои вопросы: -
- Есть ли какой-нибудь способ для нас выполнить транзакцию через несколько «таблиц», точно так же, как я могу сделать это через PostgresSQL, без необходимости использовать хранилище данных с высокой репликацией? Хранилище данных «ведущий / ведомый» сделает меня достаточно счастливым, если учитывать стоимость.
- Что я могу сделать, чтобы превратить
blob_key = files.blobstore.get_blob_key(file_name)
в запросы предков? Так что это будет работать внутри транзакции? Или, короче говоря, как я могу заставить def upload
работать в транзакции?
У меня есть полный код:
import urllib
import logging
import model
import zlib
from google.appengine.api import urlfetch
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import files
from google.appengine.ext import db
from google.appengine.ext import blobstore
xg_on = db.create_transaction_options(xg=True)
class Upload(webapp.RequestHandler):
def post(self):
email = self.request.get('Email')
password = self.request.get('Passwd')
checksum = int(self.request.get('Checksum'))
version = int(self.request.get('Version'))
logintoken = self.request.get('logintoken')
logincaptcha = self.request.get('logincaptcha')
content = self.request.get('file')
if version == -1:
self.response.out.write('ERROR [invalid parameter(s)]')
return
# Ensure the uploaded content is valid.
if content is None or not content:
self.response.out.write('ERROR [no file is uploaded]')
return
# Authentication.
headers = {"Content-type": "application/x-www-form-urlencoded"}
if logintoken and logincaptcha:
form_data = urllib.urlencode({
'accountType': 'HOSTED_OR_GOOGLE',
'Email': email,
'Passwd': password,
'service': 'mail',
'source': 'JStock-1.05b',
'logintoken': logintoken,
'logincaptcha': logincaptcha
})
else:
form_data = urllib.urlencode({
'accountType': 'HOSTED_OR_GOOGLE',
'Email': email,
'Passwd': password,
'service': 'mail',
'source': 'JStock-1.05b'
})
result = urlfetch.fetch(url='https://www.google.com/accounts/ClientLogin', payload=form_data, method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'})
self.response.set_status(result.status_code)
if result.status_code != 200:
# Fail. Either incorrect password or captcha information required.
self.response.out.write(result.content)
return
# OK! This is a valid user. Let's proceed with checksum verification.
##if checksum != zlib.adler32(content):
## self.response.out.write('ERROR [fail in checksum]')
## return
#db.run_in_transaction(self.upload, email, checksum, version, content)
db.run_in_transaction_options(xg_on, self.upload, email, checksum, version, content)
#self.upload(email, checksum, version, content)
def upload(self, email, checksum, version, content):
# Create the file
file_name = files.blobstore.create(mime_type='application/octet-stream', _blobinfo_uploaded_filename=email)
# Open the file and write to it
with files.open(file_name, 'a') as f:
f.write(content)
# Finalize the file. Do this before attempting to read it.
files.finalize(file_name)
# Get the file's blob key
blob_key = files.blobstore.get_blob_key(file_name)
# Remove previous blob referenced by this human.
query = model.Human.all()
query.filter('email =', email)
for q in query:
blobstore.delete(q.content.key())
human = model.Human(key_name=email, email=email, checksum=checksum, version=version, content=blob_key)
human.put()
application = webapp.WSGIApplication([
('/upload.py', Upload)
], debug=True)
def main():
run_wsgi_app(application)
if __name__ == '__main__':
main()