Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dropbox/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import

from ._http import build_range_headers as build_range_headers
from dropbox.dropbox_client import ( # noqa: F401 # pylint: disable=unused-import
__version__,
Dropbox,
Expand Down
43 changes: 43 additions & 0 deletions dropbox/_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
def format_byte_range(byte_range):
"""Format a byte range as an HTTP Range header value."""

if byte_range is None:
return None

if not isinstance(byte_range, tuple) or len(byte_range) != 2:
raise ValueError("byte_range must be a (start, end) tuple")

start, end = byte_range

if start is None and end is None:
raise ValueError("byte_range must specify start or end")

for value in (start, end):
if value is None:
continue
if isinstance(value, bool) or not isinstance(value, int):
raise TypeError("byte_range values must be non-negative integers")
if value < 0:
raise ValueError("byte_range values must be non-negative")

if start is not None and end is not None and end < start:
raise ValueError("byte_range end must be greater than or equal to start")

if start is None:
return "bytes=-{}".format(end)

if end is None:
return "bytes={}-".format(start)

return "bytes={}-{}".format(start, end)


def build_range_headers(byte_range):
"""Build HTTP Range headers for a download request."""

range_header = format_byte_range(byte_range)

if range_header is None:
return None

return {"Range": range_header}
12 changes: 9 additions & 3 deletions dropbox/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class DropboxBase(object):
__metaclass__ = ABCMeta

@abstractmethod
def request(self, route, namespace, request_arg, request_binary, timeout=None):
def request(
self, route, namespace, request_arg, request_binary, timeout=None, extra_headers=None
):
pass

# ------------------------------------------
Expand Down Expand Up @@ -1491,13 +1493,14 @@ def files_delete_batch_check(self, async_job_id):
)
return r

def files_download(self, path, rev=None):
def files_download(self, path, rev=None, extra_headers=None):
"""
Download a file from a user's Dropbox.
Route attributes:
scope: files.content.read
:param object extra_headers: Additional HTTP headers for this request.
:param path: The path of the file to download.
:type path: str
:param rev: Field is deprecated. Please specify revision in ``path``
Expand All @@ -1522,17 +1525,19 @@ def files_download(self, path, rev=None):
"files",
arg,
None,
extra_headers=extra_headers,
)
return r

def files_download_to_file(self, download_path, path, rev=None):
def files_download_to_file(self, download_path, path, rev=None, extra_headers=None):
"""
Download a file from a user's Dropbox.
Route attributes:
scope: files.content.read
:param str download_path: Path on local machine to save file.
:param object extra_headers: Additional HTTP headers for this request.
:param path: The path of the file to download.
:type path: str
:param rev: Field is deprecated. Please specify revision in ``path``
Expand All @@ -1550,6 +1555,7 @@ def files_download_to_file(self, download_path, path, rev=None):
"files",
arg,
None,
extra_headers=extra_headers,
)
self._save_body_to_file(download_path, r[1])
return r[0]
Expand Down
27 changes: 24 additions & 3 deletions dropbox/dropbox_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,15 @@ def clone(
),
)

def request(self, route, namespace, request_arg, request_binary, timeout=None):
def request(
self,
route,
namespace,
request_arg,
request_binary,
timeout=None,
extra_headers=None,
):
"""
Makes a request to the Dropbox API and in the process validates that
the route argument and result are the expected data types. The
Expand All @@ -304,6 +312,7 @@ def request(self, route, namespace, request_arg, request_binary, timeout=None):
server. After the timeout the client will give up on
connection. If `None`, will use default timeout set on
Dropbox object. Defaults to `None`.
:param dict extra_headers: Additional HTTP headers for this request.
:return: The route's result.
"""

Expand Down Expand Up @@ -338,6 +347,7 @@ def request(self, route, namespace, request_arg, request_binary, timeout=None):
auth_type,
request_binary,
timeout=timeout,
extra_headers=extra_headers,
)
decoded_obj_result = json.loads(res.obj_result)
if isinstance(res, RouteResult):
Expand Down Expand Up @@ -521,6 +531,7 @@ def request_json_string_with_retry(
auth_type,
request_binary,
timeout=None,
extra_headers=None,
):
"""
See :meth:`request_json_object` for description of parameters.
Expand All @@ -542,6 +553,7 @@ def request_json_string_with_retry(
auth_type,
request_binary,
timeout=timeout,
extra_headers=extra_headers,
)
except AuthError as e:
if e.error and e.error.is_expired_access_token():
Expand Down Expand Up @@ -591,6 +603,7 @@ def request_json_string(
auth_type,
request_binary,
timeout=None,
extra_headers=None,
):
"""
See :meth:`request_json_string_with_retry` for description of
Expand All @@ -611,11 +624,13 @@ def request_json_string(
url = self._get_route_url(fq_hostname, func_name)

headers = {"User-Agent": self._user_agent}
managed_auth_header = None

auth_types = auth_type.replace(" ", "").split(",")
if (USER_AUTH in auth_types or TEAM_AUTH in auth_types) and self._oauth2_access_token:
headers["Authorization"] = "Bearer %s" % self._oauth2_access_token
if self._headers:
headers.update(self._headers)
managed_auth_header = "Bearer %s" % self._oauth2_access_token
elif APP_AUTH in auth_types:
if self._app_key is None or self._app_secret is None:
raise BadInputException(
Expand All @@ -624,14 +639,20 @@ def request_json_string(
auth_header = base64.b64encode(
"{}:{}".format(self._app_key, self._app_secret).encode("utf-8")
)
headers["Authorization"] = "Basic {}".format(auth_header.decode("utf-8"))
if self._headers:
headers.update(self._headers)
managed_auth_header = "Basic {}".format(auth_header.decode("utf-8"))
elif auth_type == NO_AUTH:
pass
else:
raise BadInputException("Unhandled auth type: {}".format(auth_type))

if extra_headers:
headers.update(extra_headers)

if managed_auth_header:
headers["Authorization"] = managed_auth_header

# The contents of the body of the HTTP request
body = None
# Whether the response should be streamed incrementally, or buffered
Expand Down
12 changes: 11 additions & 1 deletion generate_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ def main():

o = subprocess.check_output(
(
["python", "-m", "stone.cli", "python_client", dropbox_pkg_path]
[
"python",
"-m",
"stone.cli",
os.path.join(
os.path.dirname(__file__),
"generator",
"dropbox_python_client.stoneg.py",
),
dropbox_pkg_path,
]
+ specs
+ ["-a", "host", "-a", "style", "-a", "auth", "-a", "scope"]
+ [
Expand Down
Loading