Запись файлов в аккаунт Dropbox из GAE
Я пытаюсь создать файлы в папке Dropbox.com из приложения GAE.
Я сделал все шаги, чтобы зарегистрировать приложение Dropbox и установил Python SDK из Dropbox локально на моей машине разработки. (см. API Dropbox.com).
Все это отлично работает, когда я использую тест cli_client.py script в SDK Dropbox на моем локальном компьютере для доступа к Dropbox - может "поместить" файлы и т.д.
Теперь я хочу начать работать в среде GAE, поэтому все становится немного сложнее.
Некоторая помощь будет полезна.
Для тех, кто знаком с кодом API Dropbox, до сих пор у меня были следующие проблемы:
Проблема 1
Модуль rest.py Dropbox API использует pkg_resources для получения сертификатов, установленных в пакетах сайтов локальной установки компьютера.
Я заменил
TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
с
TRUSTED_CERT_FILE = file('trusted-certs.crt')
и поместил файл сертификата в мою папку приложений GAE. Возможно, это не совсем правильно; см. мой код ошибки аутентификации ниже.
Проблема 2
В модуле API-интерфейса Dropbox API используется модуль oauth, поэтому я изменил include для appengine oauth.
Но возникло исключение, что GAE oauth не имеет метода OAuthConsumer, используемого модулем Dropbox session.py. Поэтому я загрузил oauth 1.0 и добавил в свое приложение теперь импортировать это вместо GAE oauth.
Проблема 3
Модуль GAE ssl не имеет свойства CERT_REQUIRED.
Это константа, поэтому я изменил
self.cert_reqs = ssl.CERT_REQUIRED
to
self.cert_reqs = 2
Это используется при вызове
ssl.wrap_socket(sock, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs)
Ошибка аутентификации
Но я до сих пор не могу подключиться к Dropbox:
Status: 401
Reason: Unauthorized
Body: {"error": "Authentication failed"}
Headers: [('date', 'Sun, 19 Feb 2012 15:11:12 GMT'), ('transfer-encoding', 'chunked'), ('connection', 'keep-alive'), ('content-type', 'application/json'), ('server', 'dbws')]
Ответы
Ответ 1
Здесь моя исправленная версия Dropbox Python SDK 1.4, которая хорошо работает для меня с Python 2.7 GAE: dropbox_python_sdk_gae_patched.7z.base64. Никаких дополнительных сторонних библиотек не требуется, только те, которые предоставляются средой GAE.
Проверяется только загрузка файлов (put_file). Вот шаги настройки:
- Распакуйте архив в корневую папку приложения GAE (если основное приложение находится в корневой папке). Вы можете декодировать BASE64 с помощью Base64 Encoder/Decoder:
base64.exe -d dropbox_python_sdk_gae_patched.7z.base64 dropbox_python_sdk_gae_patched.7z
.
- Установите APP_KEY, APP_SECRET, ACCESS_TYPE, ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET. Первые три настроены на время создания приложения Dropbox. Последние два получены при предоставлении доступа приложения к конкретной учетной записи Dropbox, вы можете получить их через cli_client.py(из SDK Python) из файла token_store.txt.
-
Используйте в коде:
import dropbox
# ...
def DropboxUpload(path, data):
sess = dropbox.session.DropboxSession(APP_KEY, APP_SECRET, ACCESS_TYPE)
sess.set_token(ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET)
cli = dropbox.client.DropboxClient(sess)
data_file = StringIO.StringIO(data)
return cli.put_file(path, data_file)
# ...
import json
class DropboxUploadHandlerExample(webapp2.RequestHandler):
def get(self):
url = "http://www.google.com/"
result = urlfetch.fetch(url)
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(json.dumps(DropboxUpload('/fetch_result.dat', result.content)))
Ответ 2
Я успешно загрузил из Google Appengine в Dropbox свою собственную исправленную версию
SDK Dropbox: https://github.com/cklein/dropbox-client-python
Использование urllib2 было заменено на huTools.http: https://github.com/hudora/huTools/
Это код, который вызывается в обработчике запросов:
db_client = dropbox.get_dropbox_client(consumer_key='', consumer_secret='', access_token_key='', access_token_secret='')
fileobj = StringIO.StringIO(data)
path = '/some/path/filename'
resp = db_client.put_file(path, fileobj)
fileobj.close()
Ответ 3
По состоянию на апрель 2016 года ни одно из других предложений не работает. (Dropbox API версии 2, Python SDK версии 6.2).
Если вам нужны только некоторые из функций SDK, мне было проще просто напрямую использовать HTTP API:
def files_upload(f, path, mode='add', autorename=False, mute=False):
args = {
'path': path,
'mode': mode,
'autorename': autorename,
'mute': mute,
}
headers = {
'Authorization': 'Bearer {}'.format(ACCESS_TOKEN),
'Dropbox-API-Arg': json.dumps(args),
'Content-Type': 'application/octet-stream',
}
request = urllib2.Request('https://content.dropboxapi.com/2/files/upload', f, headers=headers)
r = urllib2.urlopen(request)
Ответ 4
Я заработал Dropbox Python SDK версии 2.2 для работы с Google App Engine. Здесь вы найдете соответствующий код:
https://github.com/duncanhawthorne/gae-dropbox-python
Соответствующий патч кода (скопированный из github) для rest.py находится здесь:
import io
import pkg_resources
-import socket
+#import socket
import ssl
import sys
import urllib
+import urllib2
+def mock_urlopen(method,url,body,headers,preload_content):
+ request = urllib2.Request(url, body, headers=headers)
+ r = urllib2.urlopen(request)
+ return r
+
try:
import json
except ImportError:
@@ -23,7 +29,10 @@
SDK_VERSION = "2.2.0"
-TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
+try:
+ TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
+except:
+ TRUSTED_CERT_FILE = file('trusted-certs.crt')
class RESTResponse(io.IOBase):
@@ -125,6 +134,7 @@ def flush(self):
pass
def create_connection(address):
+ return
host, port = address
err = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
@@ -152,7 +162,7 @@ def json_loadb(data):
class RESTClientObject(object):
- def __init__(self, max_reusable_connections=8, mock_urlopen=None):
+ def __init__(self, max_reusable_connections=8, mock_urlopen=mock_urlopen):
"""
Parameters
max_reusable_connections
@@ -206,7 +216,7 @@ def request(self, method, url, post_params=None, body=None, headers=None, raw_re
raise ValueError("headers should not contain newlines (%s: %s)" %
(key, value))
- try:
+ if True:
# Grab a connection from the pool to make the request.
# We return it to the pool when caller close() the response
urlopen = self.mock_urlopen if self.mock_urlopen else self.pool_manager.urlopen
@@ -217,14 +227,14 @@ def request(self, method, url, post_params=None, body=None, headers=None, raw_re
headers=headers,
preload_content=False
)
- r = RESTResponse(r) # wrap up the urllib3 response before proceeding
- except socket.error as e:
- raise RESTSocketError(url, e)
- except urllib3.exceptions.SSLError as e:
- raise RESTSocketError(url, "SSL certificate error: %s" % e)
+ #r = RESTResponse(r) # wrap up the urllib3 response before proceeding
+ #except socket.error as e:
+ # raise RESTSocketError(url, e)
+ #except urllib3.exceptions.SSLError as e:
+ # raise RESTSocketError(url, "SSL certificate error: %s" % e)
- if r.status not in (200, 206):
- raise ErrorResponse(r, r.read())
+ #if r.status not in (200, 206):
+ # raise ErrorResponse(r, r.read())
return self.process_response(r, raw_response)
@@ -321,10 +331,11 @@ def PUT(cls, *n, **kw):
return cls.IMPL.PUT(*n, **kw)
-class RESTSocketError(socket.error):
+class RESTSocketError():
"""A light wrapper for ``socket.error`` that adds some more information."""
def __init__(self, host, e):
+ return
msg = "Error connecting to \"%s\": %s" % (host, str(e))
socket.error.__init__(self, msg)