Skip to content

Commit e9f7890

Browse files
Add download_backedup_asset helper method
Add `versions` parameter to `resource` Admin API Add `versions` parameter to `restore` Admin API
1 parent 8d6c1ee commit e9f7890

File tree

6 files changed

+86
-14
lines changed

6 files changed

+86
-14
lines changed

cloudinary/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def resource(public_id, **options):
105105
uri = ["resources", resource_type, upload_type, public_id]
106106
params = only(options, "exif", "faces", "colors", "image_metadata", "cinemagraph_analysis",
107107
"pages", "phash", "coordinates", "max_results", "quality_analysis", "derived_next_cursor",
108-
"accessibility_analysis")
108+
"accessibility_analysis", "versions")
109109
return call_api("get", uri, params, **options)
110110

111111

@@ -335,8 +335,8 @@ def restore(public_ids, **options):
335335
resource_type = options.pop("resource_type", "image")
336336
upload_type = options.pop("type", "upload")
337337
uri = ["resources", resource_type, upload_type, "restore"]
338-
params = dict(public_ids=public_ids)
339-
return call_api("post", uri, params, **options)
338+
params = dict(public_ids=public_ids, **only(options, "versions"))
339+
return call_json_api("post", uri, params, **options)
340340

341341

342342
def upload_mappings(**options):

cloudinary/api_client/call_api.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ def call_metadata_api(method, uri, params, **options):
2222
return call_json_api(method, uri, params, **options)
2323

2424

25-
def call_json_api(method, uri, jsonBody, **options):
26-
logger.debug(jsonBody)
27-
data = json.dumps(jsonBody).encode('utf-8')
28-
return _call_api(method, uri, body=data,
29-
headers={'Content-Type': 'application/json'}, **options)
25+
def call_json_api(method, uri, json_body, **options):
26+
data = json.dumps(json_body).encode('utf-8')
27+
return _call_api(method, uri, body=data, headers={'Content-Type': 'application/json'}, **options)
3028

3129

3230
def call_api(method, uri, params, **options):

cloudinary/utils.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from datetime import datetime, date
1616
from fractions import Fraction
1717
from numbers import Number
18-
from urllib3 import ProxyManager, PoolManager
1918

2019
import six.moves.urllib.parse
2120
from six import iteritems
21+
from urllib3 import ProxyManager, PoolManager
2222

2323
import cloudinary
2424
from cloudinary import auth_token
@@ -726,15 +726,23 @@ def cloudinary_url(source, **options):
726726
return source, options
727727

728728

729-
def cloudinary_api_url(action='upload', **options):
730-
cloudinary_prefix = options.get("upload_prefix", cloudinary.config().upload_prefix)\
729+
def base_api_url(path, **options):
730+
cloudinary_prefix = options.get("upload_prefix", cloudinary.config().upload_prefix) \
731731
or "https://api.cloudinary.com"
732732
cloud_name = options.get("cloud_name", cloudinary.config().cloud_name)
733+
733734
if not cloud_name:
734735
raise ValueError("Must supply cloud_name")
736+
737+
path = build_array(path)
738+
739+
return encode_unicode_url("/".join([cloudinary_prefix, cloudinary.API_VERSION, cloud_name] + path))
740+
741+
742+
def cloudinary_api_url(action='upload', **options):
735743
resource_type = options.get("resource_type", "image")
736744

737-
return encode_unicode_url("/".join([cloudinary_prefix, "v1_1", cloud_name, resource_type, action]))
745+
return base_api_url([resource_type, action], **options)
738746

739747

740748
def cloudinary_scaled_url(source, width, transformation, options):
@@ -848,6 +856,31 @@ def download_zip_url(**options):
848856
return download_archive_url(**new_options)
849857

850858

859+
def download_backedup_asset(asset_id, version_id, **options):
860+
"""
861+
The returned url allows downloading the backedup asset based on the the asset ID and the version ID.
862+
863+
Parameters asset_id and version_id are returned with api.resource(<PUBLIC_ID1>, versions=True) API call.
864+
865+
:param asset_id: The asset ID of the asset.
866+
:type asset_id: str
867+
:param version_id: The version ID of the asset.
868+
:type version_id: str
869+
:param options: Additional options.
870+
:type options: dict, optional
871+
:return:The signed URL for downloading backup version of the asset.
872+
:rtype: str
873+
"""
874+
params = {
875+
"timestamp": options.get("timestamp", now()),
876+
"asset_id": asset_id,
877+
"version_id": version_id
878+
}
879+
cloudinary_params = sign_request(params, options)
880+
881+
return base_api_url("download_backup", **options) + "?" + urlencode(bracketize_seq(cloudinary_params), True)
882+
883+
851884
def generate_auth_token(**options):
852885
token_options = merge(cloudinary.config().auth_token, options)
853886
return auth_token.generate(**token_options)

test/helper_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: latin-1 -*-
2+
import json
23
from contextlib import contextmanager
34
import os
45
import random
@@ -67,6 +68,10 @@ def get_params(args):
6768
return args[2] or {}
6869

6970

71+
def get_json_body(mocker):
72+
return json.loads(mocker.call_args[1]["body"].decode('utf-8') or {})
73+
74+
7075
def get_param(mocker, name):
7176
"""
7277
Return the value of the parameter

test/test_api.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from cloudinary import api, uploader, utils
1111
from test.helper_test import SUFFIX, TEST_IMAGE, get_uri, get_params, get_list_param, get_param, TEST_DOC, get_method, \
1212
UNIQUE_TAG, api_response_mock, ignore_exception, cleanup_test_resources_by_tag, cleanup_test_transformation, \
13-
cleanup_test_resources, UNIQUE_TEST_FOLDER, EVAL_STR
13+
cleanup_test_resources, UNIQUE_TEST_FOLDER, EVAL_STR, get_json_body
1414
from cloudinary.exceptions import BadRequest, NotFound
1515

1616
MOCK_RESPONSE = api_response_mock()
@@ -230,7 +230,20 @@ def test07b_resource_allows_derived_next_cursor_parameter(self, mocker):
230230
api.resource(API_TEST_ID, derived_next_cursor=NEXT_CURSOR)
231231
args, kwargs = mocker.call_args
232232
self.assertTrue("derived_next_cursor" in get_params(args))
233-
233+
234+
@patch('urllib3.request.RequestMethods.request')
235+
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
236+
def test07c_resource_allows_versions(self, mocker):
237+
""" should allow versions parameter """
238+
mocker.return_value = MOCK_RESPONSE
239+
240+
api.resource(API_TEST_ID, versions=True)
241+
242+
params = get_params(mocker.call_args[0])
243+
244+
self.assertIn("versions", params)
245+
self.assertTrue(params["versions"])
246+
234247
@patch('urllib3.request.RequestMethods.request')
235248
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
236249
def test08_delete_derived(self, mocker):
@@ -757,6 +770,21 @@ def test_restore(self):
757770
self.assertNotEqual(resource, None)
758771
self.assertEqual(resource["bytes"], 3381)
759772

773+
@patch('urllib3.request.RequestMethods.request')
774+
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
775+
def test_restore_versions(self, mocker):
776+
mocker.return_value = MOCK_RESPONSE
777+
778+
public_ids = ["pub1", "pub2"]
779+
versions = ["ver1", "ver2"]
780+
781+
api.restore(public_ids, versions=versions)
782+
783+
json_body = get_json_body(mocker)
784+
785+
self.assertListEqual(public_ids, json_body["public_ids"])
786+
self.assertListEqual(versions, json_body["versions"])
787+
760788
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
761789
def test_upload_mapping(self):
762790

test/test_archive.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ def test_create_archive_multiple_resource_types(self, mocker):
111111
self.assertTrue(get_uri(args).endswith('/auto/generate_archive'))
112112
self.assertEqual(test_ids, get_list_param(mocker, 'fully_qualified_public_ids'))
113113

114+
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
115+
def test_download_backedup_asset(self):
116+
download_backedup_asset_url = utils.download_backedup_asset('b71b23d9c89a81a254b88a91a9dad8cd',
117+
'0e493356d8a40b856c4863c026891a4e')
118+
119+
self.assertIn("asset_id", download_backedup_asset_url)
120+
self.assertIn("version_id", download_backedup_asset_url)
121+
114122

115123
if __name__ == '__main__':
116124
unittest.main()

0 commit comments

Comments
 (0)