Skip to content

Commit b10c413

Browse files
author
rob
committed
added helper methods for path_root header
1 parent e8d4502 commit b10c413

File tree

3 files changed

+136
-10
lines changed

3 files changed

+136
-10
lines changed

dropbox/dropbox.py

+66-10
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@
2222
AuthError_validator,
2323
RateLimitError_validator,
2424
)
25+
from .common import (
26+
PathRoot,
27+
PathRoot_validator,
28+
PathRootError_validator
29+
)
2530
from .base import DropboxBase
2631
from .base_team import DropboxTeamBase
2732
from .exceptions import (
2833
ApiError,
2934
AuthError,
3035
BadInputError,
3136
HttpError,
37+
PathRootError,
3238
InternalServerError,
3339
RateLimitError,
3440
)
@@ -42,6 +48,9 @@
4248
pinned_session,
4349
)
4450

51+
PATH_ROOT_HEADER = 'Dropbox-API-Path-Root'
52+
HTTP_STATUS_INVALID_PATH_ROOT = 422
53+
4554
class RouteResult(object):
4655
"""The successful result of a call to a route."""
4756

@@ -182,6 +191,26 @@ def __init__(self,
182191

183192
self._timeout = timeout
184193

194+
def clone(
195+
self,
196+
oauth2_access_token=None,
197+
max_retries_on_error=None,
198+
max_retries_on_rate_limit=None,
199+
user_agent=None,
200+
session=None,
201+
headers=None,
202+
timeout=None):
203+
204+
return self.__class__(
205+
oauth2_access_token or self._oauth2_access_token,
206+
max_retries_on_error or self._max_retries_on_error,
207+
max_retries_on_rate_limit or self._max_retries_on_rate_limit,
208+
user_agent or self._user_agent,
209+
session or self._session,
210+
headers or self._headers,
211+
timeout or self._timeout
212+
)
213+
185214
def request(self,
186215
route,
187216
namespace,
@@ -421,6 +450,10 @@ def request_json_string(self,
421450
err = stone_serializers.json_compat_obj_decode(
422451
AuthError_validator, r.json()['error'])
423452
raise AuthError(request_id, err)
453+
elif r.status_code == HTTP_STATUS_INVALID_PATH_ROOT:
454+
err = stone_serializers.json_compat_obj_decode(
455+
PathRootError_validator, r.json()['error'])
456+
raise PathRootError(request_id, err)
424457
elif r.status_code == 429:
425458
err = None
426459
if r.headers.get('content-type') == 'application/json':
@@ -479,13 +512,44 @@ def _save_body_to_file(self, download_path, http_resp, chunksize=2**16):
479512
for c in http_resp.iter_content(chunksize):
480513
f.write(c)
481514

515+
def with_path_root(self, mode, namespace_id=None):
516+
"""
517+
Creates a clone of the Dropbox instance with the Dropbox-API-Path-Root header
518+
as the appropriate serialized instance of PathRoot.
519+
520+
For more information, see
521+
https://www.dropbox.com/developers/reference/namespace-guide#pathrootmodes
522+
523+
:param str mode: path root mode to use. Must be one of "home", "root", or "namespace_id"
524+
:param str namespace_id: A namespace ID used in conjuntion with "root" or "namespace_id"
525+
modes
526+
:return: A :class: `Dropbox`
527+
:rtype: Dropbox
528+
"""
529+
530+
if mode == 'root':
531+
path_root = PathRoot.root(namespace_id)
532+
elif mode == 'namespace_id':
533+
path_root = PathRoot.namespace_id(namespace_id)
534+
elif mode == 'home':
535+
path_root = PathRoot.home
536+
else:
537+
raise ValueError("Invalid value for 'mode'. Must be one of 'home'" +
538+
", 'root' or 'namespace_id'")
539+
540+
return self.clone(
541+
headers={
542+
PATH_ROOT_HEADER: stone_serializers.json_encode(PathRoot_validator, path_root)
543+
}
544+
)
545+
482546
class Dropbox(_DropboxTransport, DropboxBase):
483547
"""
484548
Use this class to make requests to the Dropbox API using a user's access
485549
token. Methods of this class are meant to act on the corresponding user's
486550
Dropbox.
487551
"""
488-
pass
552+
489553

490554
class DropboxTeam(_DropboxTransport, DropboxTeamBase):
491555
"""
@@ -532,12 +596,4 @@ def _get_dropbox_client_with_select_header(self, select_header_name, team_member
532596

533597
new_headers = self._headers.copy() if self._headers else {}
534598
new_headers[select_header_name] = team_member_id
535-
return Dropbox(
536-
self._oauth2_access_token,
537-
max_retries_on_error=self._max_retries_on_error,
538-
max_retries_on_rate_limit=self._max_retries_on_rate_limit,
539-
timeout=self._timeout,
540-
user_agent=self._raw_user_agent,
541-
session=self._session,
542-
headers=new_headers,
543-
)
599+
return self.clone(headers=new_headers)

dropbox/exceptions.py

+11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ def __repr__(self):
4646
self.status_code, self.body)
4747

4848

49+
class PathRootError(HttpError):
50+
"""Error caused by an invalid path root."""
51+
52+
def __init__(self, request_id, error=None):
53+
super(PathRootError, self).__init__(request_id, 422, None)
54+
self.error = error
55+
56+
def __repr__(self):
57+
return 'PathRootError({!r}, {!r})'.format(self.request_id, self.error)
58+
59+
4960
class BadInputError(HttpError):
5061
"""Errors due to bad input parameters to an API Operation."""
5162

test/test_dropbox.py

+59
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@
2222
DropboxOAuth2Flow,
2323
DropboxTeam,
2424
session,
25+
stone_serializers,
2526
)
27+
from dropbox.dropbox import PATH_ROOT_HEADER
2628
from dropbox.exceptions import (
2729
ApiError,
2830
AuthError,
2931
BadInputError,
32+
PathRootError,
3033
)
3134
from dropbox.files import (
3235
ListFolderError,
3336
)
37+
from dropbox.common import (
38+
PathRoot,
39+
PathRoot_validator,
40+
)
3441

3542
def _token_from_env_or_die(env_name='DROPBOX_TOKEN'):
3643
oauth2_token = os.environ.get(env_name)
@@ -137,5 +144,57 @@ def test_team(self, dbxt):
137144
team_member_id = r.members[0].profile.team_member_id
138145
dbxt.as_user(team_member_id).files_list_folder('')
139146

147+
@dbx_from_env
148+
def test_with_path_root_constructor(self, dbx):
149+
# Verify valid mode types
150+
for mode, ns_id in (
151+
("home", None),
152+
("root", "123"),
153+
("namespace_id", "123")
154+
):
155+
dbx_new = dbx.with_path_root(mode, ns_id)
156+
self.assertIsNot(dbx_new, dbx)
157+
158+
path_root_mode = getattr(PathRoot, mode)
159+
path_root = path_root_mode(ns_id) if ns_id else path_root_mode
160+
161+
expected = stone_serializers.json_encode(PathRoot_validator, path_root)
162+
self.assertEqual(dbx_new._headers.get(PATH_ROOT_HEADER), expected)
163+
164+
# verify invalid mode raises ValueError
165+
with self.assertRaises(ValueError):
166+
dbx.with_path_root("invalid_mode")
167+
168+
@dbx_from_env
169+
def test_path_root(self, dbx):
170+
root_info = dbx.users_get_current_account().root_info
171+
root_ns = root_info.root_namespace_id
172+
home_ns = root_info.home_namespace_id
173+
174+
# verify home mode
175+
dbxpr = dbx.with_path_root('home')
176+
dbxpr.files_list_folder('')
177+
178+
# verify root mode
179+
dbxpr = dbx.with_path_root("root", root_ns)
180+
dbxpr.files_list_folder('')
181+
182+
# verify namespace_id mode
183+
dbxpr = dbx.with_path_root("namespace_id", home_ns)
184+
dbxpr.files_list_folder('')
185+
186+
@dbx_from_env
187+
def test_path_root_err(self, dbx):
188+
# verify invalid namespace return is_no_permission error
189+
dbxpr = dbx.with_path_root("namespace_id", "1234567890")
190+
with self.assertRaises(PathRootError) as cm:
191+
dbxpr.files_list_folder('')
192+
self.assertTrue(cm.exception.error.is_no_permission())
193+
194+
dbxpr = dbx.with_path_root("root", "1234567890")
195+
with self.assertRaises(PathRootError) as cm:
196+
dbxpr.files_list_folder('')
197+
self.assertTrue(cm.exception.error.is_invalid_root())
198+
140199
if __name__ == '__main__':
141200
unittest.main()

0 commit comments

Comments
 (0)