Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6ffbff0

Browse files
authored
Merge pull request #7 from smartfile/sdk-update
Python SDK update. Closes #3685
2 parents 04ff2ce + c626a20 commit 6ffbff0

File tree

7 files changed

+295
-49
lines changed

7 files changed

+295
-49
lines changed

.gitignore

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
27+
# PyInstaller
28+
# Usually these files are written by a python script from a template
29+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.coverage
41+
.coverage.*
42+
.cache
43+
nosetests.xml
44+
coverage.xml
45+
*,cover
46+
.hypothesis/
47+
48+
# Translations
49+
*.mo
50+
*.pot
51+
52+
# Django stuff:
53+
*.log
54+
local_settings.py
55+
56+
# Flask stuff:
57+
instance/
58+
.webassets-cache
59+
60+
# Scrapy stuff:
61+
.scrapy
62+
63+
# Sphinx documentation
64+
docs/_build/
65+
66+
# PyBuilder
67+
target/
68+
69+
# IPython Notebook
70+
.ipynb_checkpoints
71+
72+
# pyenv
73+
.python-version
74+
75+
# celery beat schedule file
76+
celerybeat-schedule
77+
78+
# dotenv
79+
.env
80+
81+
# virtualenv
82+
venv/
83+
ENV/
84+
85+
# Spyder project settings
86+
.spyderproject
87+
88+
# Rope project settings
89+
.ropeproject
90+
91+
main.py
92+
93+
/test/resources

README.rst

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ Authentication using OAuth authentication is bit more complicated, as it involve
142142
>>> from smartfile import OAuthClient
143143
>>> api = OAuthClient('**********', '**********')
144144
>>> # Be sure to only call each method once for each OAuth login
145-
>>>
145+
>>>
146146
>>> # This is the first step with the client, which should be left alone
147147
>>> api.get_request_token()
148148
>>> # Redirect users to the following URL:
@@ -199,31 +199,26 @@ File transfers
199199

200200
Uploading and downloading files is supported.
201201

202-
To upload a file, pass either a file-like object or a tuple of
203-
``(filename, file-like)`` as a kwarg.
202+
To upload a file:
204203

205204
.. code:: python
206205
207-
>>> from StringIO import StringIO
208-
>>> data = StringIO('StringIO instance has no .name attribute!')
209206
>>> from smartfile import BasicClient
210207
>>> api = BasicClient()
211-
>>> api.post('/path/data/', file=('foobar.png', data))
212-
>>> # Or use a file-like object with a name attribute
213-
>>> api.post('/path/data/', file=file('foobar.png', 'rb'))
208+
>>> file = open('test.txt', 'rb')
209+
>>> api.upload('test.txt', file)
210+
214211
215212
Downloading is automatic, if the ``'Content-Type'`` header indicates
216213
content other than the expected JSON return value, then a file-like object is
217214
returned.
218215

219216
.. code:: python
220217
221-
>>> import shutil
222218
>>> from smartfile import BasicClient
223219
>>> api = BasicClient()
224-
>>> f = api.get('/path/data/', 'foobar.png')
225-
>>> with file('foobar.png', 'wb') as o:
226-
>>> shutil.copyfileobj(f, o)
220+
>>> api.download('foobar.png')
221+
227222
228223
Tasks
229224
-----
@@ -232,16 +227,53 @@ Operations are long-running jobs that are not executed within the time frame
232227
of an API call. For such operations, a task is created, and the API can be used
233228
to poll the status of the task.
234229

230+
Move files
231+
235232
.. code:: python
236233
234+
>>> import logging
237235
>>> from smartfile import BasicClient
236+
>>>
238237
>>> api = BasicClient()
239-
>>> t = api.post('/path/oper/move/', src='/foobar.png', dst='/images/foobar.png')
238+
>>>
239+
>>> LOGGER = logging.getLogger(__name__)
240+
>>> LOGGER.setLevel(logging.INFO)
241+
>>>
242+
>>> api.move('file.txt', '/newFolder')
243+
>>>
240244
>>> while True:
241-
>>> s = api.get('/task', t['uuid'])
242-
>>> if s['status'] == 'SUCCESS':
245+
>>> try:
246+
>>> s = api.get('/task', api['uuid'])
247+
>>> # Sleep to assure the user does not get rate limited
248+
>>> time.sleep(1)
249+
>>> if s['result']['status'] == 'SUCCESS':
250+
>>> break
251+
>>> elif s['result']['status'] == 'FAILURE':
252+
>>> LOGGER.info("Task failure: " + s['uuid'])
253+
>>> except Exception as e:
254+
>>> print e
243255
>>> break
244256
245257
258+
Delete files
259+
260+
.. code:: python
261+
262+
>>> from smartfile import BasicClient
263+
>>> api = BasicClient()
264+
>>> api.remove('foobar.png')
265+
246266
.. _SmartFile: http://www.smartfile.com/
247267
.. _Read more: http://www.smartfile.com/open-source.html
268+
269+
270+
271+
Running Tests
272+
--------------
273+
To run tests for the test.py file:
274+
::
275+
nosetests -v tests.py
276+
277+
To run tests for the test_smartfile.py file:
278+
::
279+
API_KEY='****' API_PASSWORD='****' nosetests test

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import os
44
import re
5-
#from distutils.core import setup
65
from setuptools import setup
76

87
VERSION_PATTERN = re.compile(r'^[^#]*__version__\W*\=\W*["\'](.*)["\']')

smartfile/__init__.py

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
from netrc import netrc
21
import os
32
import re
3+
import shutil
44
import time
55
import urllib
6+
7+
from netrc import netrc
68
try:
79
import urlparse
810
# Fixed pyflakes warning...
911
urlparse
1012
except ImportError:
1113
from urllib import parse as urlparse
1214

13-
1415
import requests
1516
from requests.exceptions import RequestException
1617

@@ -54,7 +55,8 @@ def _do_request(self, request, url, **kwargs):
5455
else:
5556
if response.status_code >= 400:
5657
raise ResponseError(response)
57-
# Try to return the response in the most useful fashion given it's type.
58+
# Try to return the response in the most useful fashion given it's
59+
# type.
5860
if response.headers.get('content-type') == 'application/json':
5961
try:
6062
# Try to decode as JSON
@@ -94,18 +96,21 @@ def _request(self, method, endpoint, id=None, **kwargs):
9496
path = path.replace('//', '/')
9597
url = self.url + path
9698
# Add our user agent.
97-
kwargs.setdefault('headers', {}).setdefault('User-Agent', HTTP_USER_AGENT)
99+
kwargs.setdefault('headers', {}).setdefault('User-Agent',
100+
HTTP_USER_AGENT)
98101
# Now try the request, if we get throttled, sleep and try again.
99102
trys, retrys = 0, 3
100103
while True:
101104
if trys == retrys:
102-
raise RequestError('Could not complete request after %s trys.' % trys)
105+
raise RequestError('Could not complete request after %s trys.'
106+
% trys)
103107
trys += 1
104108
try:
105109
return self._do_request(request, url, **kwargs)
106110
except ResponseError as e:
107111
if self.throttle_wait and e.status_code == 503:
108-
m = THROTTLE_PATTERN.match(e.response.headers.get('x-throttle', ''))
112+
m = THROTTLE_PATTERN.match(
113+
e.response.headers.get('x-throttle', ''))
109114
if m:
110115
time.sleep(float(m.group(1)))
111116
continue
@@ -127,6 +132,42 @@ def post(self, endpoint, id=None, **kwargs):
127132
def delete(self, endpoint, id=None, **kwargs):
128133
return self._request('delete', endpoint, id=id, data=kwargs)
129134

135+
def remove(self, deletefile):
136+
try:
137+
return self.post('/path/oper/remove', path=deletefile)
138+
except KeyError:
139+
raise Exception("Destination file does not exist")
140+
141+
def upload(self, filename, fileobj):
142+
if filename.endswith('/'):
143+
filename = filename[:-1]
144+
arg = (filename, fileobj)
145+
return self.post('/path/data/', file=arg)
146+
147+
def download(self, file_to_be_downloaded):
148+
""" file_to_be_downloaded is a file-like object that has already
149+
been uploaded, you cannot download folders """
150+
# download uses shutil.copyfileobj to download, which copies
151+
# the data in chunks
152+
o = open(file_to_be_downloaded, 'wb')
153+
return shutil.copyfileobj(self.get('/path/data/',
154+
file_to_be_downloaded), o)
155+
156+
def move(self, src_path, dst_path):
157+
# check destination folder for / at end
158+
if not src_path.endswith("/"):
159+
src_path = src_path + "/"
160+
# check destination folder for / at begining
161+
if not src_path.startswith("/"):
162+
src_path = "/" + src_path
163+
# check destination folder for / at end
164+
if not dst_path.endswith("/"):
165+
dst_path = dst_path + "/"
166+
# check destination folder for / at begining
167+
if not dst_path.startswith("/"):
168+
dst_path = "/" + dst_path
169+
return self.post('/path/oper/move/', src=src_path, dst=dst_path)
170+
130171

131172
class BasicClient(Client):
132173
"""API client that uses a key and password. Layers a simple form of
@@ -193,10 +234,11 @@ def is_valid(self):
193234
return False
194235

195236
class OAuthClient(Client):
196-
"""API client that uses OAuth tokens. Layers a more complex form of
197-
authentication useful for 3rd party access on top of the base Client."""
198-
def __init__(self, client_token=None, client_secret=None, access_token=None,
199-
access_secret=None, **kwargs):
237+
"""API client that uses OAuth tokens. Layers a more complex
238+
form of authentication useful for 3rd party access on top of
239+
the base Client."""
240+
def __init__(self, client_token=None, client_secret=None,
241+
access_token=None, access_secret=None, **kwargs):
200242
if client_token is None:
201243
client_token = os.environ.get('SMARTFILE_CLIENT_TOKEN')
202244
if client_secret is None:
@@ -207,15 +249,15 @@ def __init__(self, client_token=None, client_secret=None, access_token=None,
207249
access_secret = os.environ.get('SMARTFILE_ACCESS_SECRET')
208250
self._client = OAuthToken(client_token, client_secret)
209251
if not self._client.is_valid():
210-
raise APIError('You must provide a client_token and client_secret '
211-
'for OAuth.')
252+
raise APIError('You must provide a client_token'
253+
'and client_secret for OAuth.')
212254
self._access = OAuthToken(access_token, access_secret)
213255
super(OAuthClient, self).__init__(**kwargs)
214256

215257
def _do_request(self, *args, **kwargs):
216258
if not self._access.is_valid():
217-
raise APIError('You must obtain an access token before making API '
218-
'calls.')
259+
raise APIError('You must obtain an access token'
260+
'before making API calls.')
219261
# Add the OAuth parameters.
220262
kwargs['auth'] = OAuth1(self._client.token,
221263
client_secret=self._client.secret,
@@ -230,26 +272,29 @@ def get_request_token(self, callback=None):
230272
client_secret=self._client.secret,
231273
callback_uri=callback,
232274
signature_method=SIGNATURE_PLAINTEXT)
233-
r = requests.post(urlparse.urljoin(self.url, 'oauth/request_token/'), auth=oauth)
275+
r = requests.post(urlparse.urljoin(
276+
self.url, 'oauth/request_token/'), auth=oauth)
234277
credentials = urlparse.parse_qs(r.text)
235278
self.__request = OAuthToken(credentials.get('oauth_token')[0],
236-
credentials.get('oauth_token_secret')[0])
279+
credentials.get(
280+
'oauth_token_secret')[0])
237281
return self.__request
238282

239283
def get_authorization_url(self, request=None):
240284
"The second step of the OAuth workflow."
241285
if request is None:
242286
if not self.__request.is_valid():
243-
raise APIError('You must obtain a request token to request '
244-
'and access token. Use get_request_token() '
245-
'first.')
287+
raise APIError('You must obtain a request token to'
288+
'request and access token. Use'
289+
'get_request_token() first.')
246290
request = self.__request
247291
url = urlparse.urljoin(self.url, 'oauth/authorize/')
248-
return url + '?' + urllib.urlencode(dict(oauth_token=request.token))
292+
return url + '?' + urllib.urlencode(
293+
dict(oauth_token=request.token))
249294

250295
def get_access_token(self, request=None, verifier=None):
251-
"""The final step of the OAuth workflow. After this the client can make
252-
API calls."""
296+
"""The final step of the OAuth workflow. After this the client
297+
can make API calls."""
253298
if request is None:
254299
if not self.__request.is_valid():
255300
raise APIError('You must obtain a request token to request '
@@ -262,7 +307,8 @@ def get_access_token(self, request=None, verifier=None):
262307
resource_owner_secret=request.secret,
263308
verifier=verifier,
264309
signature_method=SIGNATURE_PLAINTEXT)
265-
r = requests.post(urlparse.urljoin(self.url, 'oauth/access_token/'), auth=oauth)
310+
r = requests.post(urlparse.urljoin(
311+
self.url, 'oauth/access_token/'), auth=oauth)
266312
credentials = urlparse.parse_qs(r.text)
267313
self._access = OAuthToken(credentials.get('oauth_token')[0],
268314
credentials.get('oauth_token_secret')[0])

0 commit comments

Comments
 (0)