Skip to content

Commit a3fd60e

Browse files
author
rob
committed
with_path_root now takes PathRoot objects instead of strings (+5 squashed commits)
Squashed commits: [4c9cdf9] make linter happy [e3004ff] added a little future proofing [81f2b87] remove extra space [02dcc5f] update docstring [b10c413] added helper methods for path_root header
1 parent e8d4502 commit a3fd60e

File tree

3 files changed

+132
-9
lines changed

3 files changed

+132
-9
lines changed

dropbox/dropbox.py

+65-9
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,35 @@ 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+
Creates a new copy of the Dropbox client with the same defaults unless modified by
205+
arguments to clone()
206+
207+
See constructor for original parameter descriptions.
208+
209+
:return: New instance of Dropbox clent
210+
:rtype: Dropbox
211+
"""
212+
213+
return self.__class__(
214+
oauth2_access_token or self._oauth2_access_token,
215+
max_retries_on_error or self._max_retries_on_error,
216+
max_retries_on_rate_limit or self._max_retries_on_rate_limit,
217+
user_agent or self._user_agent,
218+
session or self._session,
219+
headers or self._headers,
220+
timeout or self._timeout
221+
)
222+
185223
def request(self,
186224
route,
187225
namespace,
@@ -421,6 +459,10 @@ def request_json_string(self,
421459
err = stone_serializers.json_compat_obj_decode(
422460
AuthError_validator, r.json()['error'])
423461
raise AuthError(request_id, err)
462+
elif r.status_code == HTTP_STATUS_INVALID_PATH_ROOT:
463+
err = stone_serializers.json_compat_obj_decode(
464+
PathRootError_validator, r.json()['error'])
465+
raise PathRootError(request_id, err)
424466
elif r.status_code == 429:
425467
err = None
426468
if r.headers.get('content-type') == 'application/json':
@@ -479,6 +521,28 @@ def _save_body_to_file(self, download_path, http_resp, chunksize=2**16):
479521
for c in http_resp.iter_content(chunksize):
480522
f.write(c)
481523

524+
def with_path_root(self, path_root):
525+
"""
526+
Creates a clone of the Dropbox instance with the Dropbox-API-Path-Root header
527+
as the appropriate serialized instance of PathRoot.
528+
529+
For more information, see
530+
https://www.dropbox.com/developers/reference/namespace-guide#pathrootmodes
531+
532+
:param PathRoot path_root: instance of PathRoot to serialize into the headers field
533+
:return: A :class: `Dropbox`
534+
:rtype: Dropbox
535+
"""
536+
537+
if not isinstance(path_root, PathRoot):
538+
raise ValueError("path_root must be an instance of PathRoot")
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
@@ -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

+56
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,54 @@ 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 path_root in (
151+
PathRoot.home,
152+
PathRoot.root("123"),
153+
PathRoot.namespace_id("123"),
154+
):
155+
dbx_new = dbx.with_path_root(path_root)
156+
self.assertIsNot(dbx_new, dbx)
157+
158+
expected = stone_serializers.json_encode(PathRoot_validator, path_root)
159+
self.assertEqual(dbx_new._headers.get(PATH_ROOT_HEADER), expected)
160+
161+
# verify invalid mode raises ValueError
162+
with self.assertRaises(ValueError):
163+
dbx.with_path_root(None)
164+
165+
@dbx_from_env
166+
def test_path_root(self, dbx):
167+
root_info = dbx.users_get_current_account().root_info
168+
root_ns = root_info.root_namespace_id
169+
home_ns = root_info.home_namespace_id
170+
171+
# verify home mode
172+
dbxpr = dbx.with_path_root(PathRoot.home)
173+
dbxpr.files_list_folder('')
174+
175+
# verify root mode
176+
dbxpr = dbx.with_path_root(PathRoot.root(root_ns))
177+
dbxpr.files_list_folder('')
178+
179+
# verify namespace_id mode
180+
dbxpr = dbx.with_path_root(PathRoot.namespace_id(home_ns))
181+
dbxpr.files_list_folder('')
182+
183+
@dbx_from_env
184+
def test_path_root_err(self, dbx):
185+
# verify invalid namespace return is_no_permission error
186+
dbxpr = dbx.with_path_root(PathRoot.namespace_id("1234567890"))
187+
with self.assertRaises(PathRootError) as cm:
188+
dbxpr.files_list_folder('')
189+
self.assertTrue(cm.exception.error.is_no_permission())
190+
191+
dbxpr = dbx.with_path_root(PathRoot.root("1234567890"))
192+
with self.assertRaises(PathRootError) as cm:
193+
dbxpr.files_list_folder('')
194+
self.assertTrue(cm.exception.error.is_invalid_root())
195+
140196
if __name__ == '__main__':
141197
unittest.main()

0 commit comments

Comments
 (0)