cloudinary 1.46.1__tar.gz → 1.46.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloudinary-1.46.1/cloudinary.egg-info → cloudinary-1.46.3}/PKG-INFO +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/__init__.py +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api.py +2 -2
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/auth_token.py +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/provisioning/account.py +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/uploader.py +46 -5
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/utils.py +14 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3/cloudinary.egg-info}/PKG-INFO +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/pyproject.toml +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/setup.py +1 -1
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_api.py +6 -3
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_auth_token.py +9 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_provisioning_api.py +16 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_uploader.py +68 -4
- {cloudinary-1.46.1 → cloudinary-1.46.3}/LICENSE +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/MANIFEST.in +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/README.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api_client/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api_client/call_account_api.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api_client/call_api.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api_client/execute_request.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/api_client/tcp_keep_alive_manager.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/adapter/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/adapter/cache_adapter.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/adapter/key_value_cache_adapter.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/responsive_breakpoints_cache.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/storage/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/storage/file_system_key_value_storage.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/storage/key_value_storage.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/compat.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/README.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/configure-cloudinary.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/get-credentials.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/import-and-call.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/moderate-upload.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/platform-capabilities.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/search-and-manage-assets.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/sign-browser-upload.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/transform-and-deliver-image.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/transform-and-deliver-video.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/troubleshoot-errors.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/upload-image.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/upload-large-video.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/use-structured-metadata.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/docs/use-with-django.md +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/exceptions.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/forms.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/http_client.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/models.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/poster/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/poster/encode.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/poster/streaminghttp.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/provisioning/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/provisioning/account_config.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/search.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/search_folders.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/html/cloudinary_cors.html +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/canvas-to-blob.min.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.cloudinary.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload-image.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload-process.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload-validate.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.iframe-transport.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.ui.widget.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/load-image.all.min.js +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/templates/cloudinary_direct_upload.html +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/templates/cloudinary_includes.html +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/templates/cloudinary_js_config.html +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/templatetags/__init__.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/templatetags/cloudinary.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary.egg-info/SOURCES.txt +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary.egg-info/dependency_links.txt +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary.egg-info/not-zip-safe +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary.egg-info/requires.txt +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary.egg-info/top_level.txt +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/setup.cfg +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_api_authorization.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_archive.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_cloudinary_resource.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_config.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_expression_normalization.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_http_client.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_image.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_metadata.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_metadata_rules.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_notifications.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_request_id.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_search.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_streaming_profiles.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_triggers.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_utils.py +0 -0
- {cloudinary-1.46.1 → cloudinary-1.46.3}/test/test_video.py +0 -0
|
@@ -38,7 +38,7 @@ CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAA
|
|
|
38
38
|
URI_SCHEME = "cloudinary"
|
|
39
39
|
API_VERSION = "v1_1"
|
|
40
40
|
|
|
41
|
-
VERSION = "1.46.
|
|
41
|
+
VERSION = "1.46.3"
|
|
42
42
|
|
|
43
43
|
_USER_PLATFORM_DETAILS = "; ".join((platform(), "Python {}".format(python_version())))
|
|
44
44
|
|
|
@@ -450,7 +450,7 @@ def update(public_id, **options):
|
|
|
450
450
|
params = only(options, "moderation_status", "raw_convert",
|
|
451
451
|
"quality_override", "ocr",
|
|
452
452
|
"categorization", "detection", "similarity_search",
|
|
453
|
-
"background_removal", "notification_url")
|
|
453
|
+
"background_removal", "notification_url", "batch_id")
|
|
454
454
|
if "tags" in options:
|
|
455
455
|
params["tags"] = ",".join(utils.build_array(options["tags"]))
|
|
456
456
|
if "face_coordinates" in options:
|
|
@@ -1567,7 +1567,7 @@ def __delete_resource_params(options, **params):
|
|
|
1567
1567
|
:internal
|
|
1568
1568
|
"""
|
|
1569
1569
|
p = dict(transformations=utils.build_eager(options.get('transformations')),
|
|
1570
|
-
**only(options, "keep_original", "next_cursor", "invalidate", "batch_id"))
|
|
1570
|
+
**only(options, "keep_original", "next_cursor", "invalidate", "batch_id", "notification_url"))
|
|
1571
1571
|
p.update(params)
|
|
1572
1572
|
return p
|
|
1573
1573
|
|
|
@@ -464,7 +464,7 @@ def user_in_user_groups(user_id, **options):
|
|
|
464
464
|
:return: List of groups user is in
|
|
465
465
|
:rtype: dict
|
|
466
466
|
"""
|
|
467
|
-
uri = [
|
|
467
|
+
uri = [USERS_SUB_PATH, user_id, "groups"]
|
|
468
468
|
return _call_account_api("get", uri, {}, **options)
|
|
469
469
|
|
|
470
470
|
|
|
@@ -41,6 +41,9 @@ upload_options = [
|
|
|
41
41
|
|
|
42
42
|
UPLOAD_LARGE_CHUNK_SIZE = 20000000
|
|
43
43
|
|
|
44
|
+
# The tags, context and metadata endpoints accept these params but omit them from signature
|
|
45
|
+
_NON_SIGNABLE_NOTIFICATION_PARAMS = ("notification_url", "batch_id")
|
|
46
|
+
|
|
44
47
|
|
|
45
48
|
def upload(file, **options):
|
|
46
49
|
"""
|
|
@@ -378,6 +381,9 @@ def destroy(public_id, **options):
|
|
|
378
381
|
:param options: Additional options for the deletion.
|
|
379
382
|
:keyword str type: The storage type (upload, private, authenticated).
|
|
380
383
|
:keyword bool invalidate: Invalidate cached copies on the CDN if True.
|
|
384
|
+
:keyword str notification_url: A URL to notify when the deletion is completed.
|
|
385
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
386
|
+
when polling.
|
|
381
387
|
:return: The result of the API call.
|
|
382
388
|
:rtype: dict
|
|
383
389
|
"""
|
|
@@ -385,7 +391,9 @@ def destroy(public_id, **options):
|
|
|
385
391
|
"timestamp": utils.now(),
|
|
386
392
|
"type": options.get("type"),
|
|
387
393
|
"invalidate": options.get("invalidate"),
|
|
388
|
-
"public_id": public_id
|
|
394
|
+
"public_id": public_id,
|
|
395
|
+
"notification_url": options.get("notification_url"),
|
|
396
|
+
"batch_id": options.get("batch_id")
|
|
389
397
|
}
|
|
390
398
|
return call_api("destroy", params, **options)
|
|
391
399
|
|
|
@@ -407,6 +415,9 @@ def rename(from_public_id, to_public_id, **options):
|
|
|
407
415
|
:keyword str to_type: Change the resource to the specified upload type.
|
|
408
416
|
:keyword dict context: Set or update contextual metadata.
|
|
409
417
|
:keyword dict metadata: Set or update structured metadata.
|
|
418
|
+
:keyword str notification_url: A URL to notify when the rename is completed.
|
|
419
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
420
|
+
when polling.
|
|
410
421
|
:return: The result of the API call.
|
|
411
422
|
:rtype: dict
|
|
412
423
|
"""
|
|
@@ -419,7 +430,9 @@ def rename(from_public_id, to_public_id, **options):
|
|
|
419
430
|
"to_public_id": to_public_id,
|
|
420
431
|
"to_type": options.get("to_type"),
|
|
421
432
|
"context": options.get("context"),
|
|
422
|
-
"metadata": options.get("metadata")
|
|
433
|
+
"metadata": options.get("metadata"),
|
|
434
|
+
"notification_url": options.get("notification_url"),
|
|
435
|
+
"batch_id": options.get("batch_id")
|
|
423
436
|
}
|
|
424
437
|
return call_api("rename", params, **options)
|
|
425
438
|
|
|
@@ -438,6 +451,9 @@ def update_metadata(metadata, public_ids, **options):
|
|
|
438
451
|
:keyword str resource_type: The resource type (image, raw, video). Default="image".
|
|
439
452
|
:keyword str type: The storage type (upload, private, authenticated).
|
|
440
453
|
:keyword bool clear_invalid: If True, remove keys that are not valid.
|
|
454
|
+
:keyword str notification_url: A URL to notify when the update is completed.
|
|
455
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
456
|
+
when polling.
|
|
441
457
|
:return: A list of public IDs that were updated.
|
|
442
458
|
:rtype: dict
|
|
443
459
|
"""
|
|
@@ -446,8 +462,11 @@ def update_metadata(metadata, public_ids, **options):
|
|
|
446
462
|
"metadata": utils.encode_context(metadata),
|
|
447
463
|
"public_ids": utils.build_array(public_ids),
|
|
448
464
|
"type": options.get("type"),
|
|
449
|
-
"clear_invalid": options.get("clear_invalid")
|
|
465
|
+
"clear_invalid": options.get("clear_invalid"),
|
|
466
|
+
"notification_url": options.get("notification_url"),
|
|
467
|
+
"batch_id": options.get("batch_id")
|
|
450
468
|
}
|
|
469
|
+
options["non_signable"] = _NON_SIGNABLE_NOTIFICATION_PARAMS
|
|
451
470
|
return call_api("metadata", params, **options)
|
|
452
471
|
|
|
453
472
|
|
|
@@ -509,6 +528,8 @@ def generate_sprite(tag=None, urls=None, **options):
|
|
|
509
528
|
:param urls: List of URLs to create a sprite from (only if tag not set).
|
|
510
529
|
:type urls: list[str], optional
|
|
511
530
|
:param options: Additional sprite configuration.
|
|
531
|
+
:keyword str public_id: The public ID to assign to the generated sprite. When omitted, the
|
|
532
|
+
server derives one from the tag or URLs. Cannot be used with mode=download.
|
|
512
533
|
:return: Dictionary with metadata and URLs of generated sprite resources.
|
|
513
534
|
:rtype: dict
|
|
514
535
|
"""
|
|
@@ -543,6 +564,8 @@ def multi(tag=None, urls=None, **options):
|
|
|
543
564
|
:param urls: A list of image URLs (if no tag is set).
|
|
544
565
|
:type urls: list[str], optional
|
|
545
566
|
:param options: Additional multi-configuration options.
|
|
567
|
+
:keyword str public_id: The public ID to assign to the generated file. When omitted, the server
|
|
568
|
+
derives one from the tag or URLs. Cannot be used with mode=download.
|
|
546
569
|
:return: Dictionary with metadata and URLs of the generated file.
|
|
547
570
|
:rtype: dict
|
|
548
571
|
"""
|
|
@@ -575,6 +598,8 @@ def explode(public_id, **options):
|
|
|
575
598
|
:param public_id: The public ID of the file to explode.
|
|
576
599
|
:type public_id: str
|
|
577
600
|
:param options: Additional explode options (format, notification_url, batch_id, transformation).
|
|
601
|
+
:keyword str type: The storage type of the file to explode (upload, private, authenticated).
|
|
602
|
+
Default=upload.
|
|
578
603
|
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
579
604
|
when polling.
|
|
580
605
|
:return: The result of the API call.
|
|
@@ -584,6 +609,7 @@ def explode(public_id, **options):
|
|
|
584
609
|
"timestamp": utils.now(),
|
|
585
610
|
"public_id": public_id,
|
|
586
611
|
"format": options.get("format"),
|
|
612
|
+
"type": options.get("type"),
|
|
587
613
|
"notification_url": options.get("notification_url"),
|
|
588
614
|
"batch_id": options.get("batch_id"),
|
|
589
615
|
"transformation": utils.generate_transformation_string(**options)[0]
|
|
@@ -705,6 +731,9 @@ def call_tags_api(tag, command, public_ids=None, **options):
|
|
|
705
731
|
:param public_ids: A list of asset public IDs.
|
|
706
732
|
:type public_ids: list[str], optional
|
|
707
733
|
:param options: Additional options (e.g., type).
|
|
734
|
+
:keyword str notification_url: A URL to notify when the update is completed.
|
|
735
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
736
|
+
when polling.
|
|
708
737
|
:return: The result of the API call.
|
|
709
738
|
:rtype: dict
|
|
710
739
|
"""
|
|
@@ -713,8 +742,11 @@ def call_tags_api(tag, command, public_ids=None, **options):
|
|
|
713
742
|
"tag": tag,
|
|
714
743
|
"public_ids": utils.build_array(public_ids),
|
|
715
744
|
"command": command,
|
|
716
|
-
"type": options.get("type")
|
|
745
|
+
"type": options.get("type"),
|
|
746
|
+
"notification_url": options.get("notification_url"),
|
|
747
|
+
"batch_id": options.get("batch_id")
|
|
717
748
|
}
|
|
749
|
+
options["non_signable"] = _NON_SIGNABLE_NOTIFICATION_PARAMS
|
|
718
750
|
return call_api("tags", params, **options)
|
|
719
751
|
|
|
720
752
|
|
|
@@ -731,6 +763,9 @@ def call_context_api(context, command, public_ids=None, **options):
|
|
|
731
763
|
:param public_ids: A list of asset public IDs.
|
|
732
764
|
:type public_ids: list[str], optional
|
|
733
765
|
:param options: Additional options (e.g., type).
|
|
766
|
+
:keyword str notification_url: A URL to notify when the update is completed.
|
|
767
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
768
|
+
when polling.
|
|
734
769
|
:return: The result of the API call.
|
|
735
770
|
:rtype: dict
|
|
736
771
|
"""
|
|
@@ -739,8 +774,11 @@ def call_context_api(context, command, public_ids=None, **options):
|
|
|
739
774
|
"context": utils.encode_context(context),
|
|
740
775
|
"public_ids": utils.build_array(public_ids),
|
|
741
776
|
"command": command,
|
|
742
|
-
"type": options.get("type")
|
|
777
|
+
"type": options.get("type"),
|
|
778
|
+
"notification_url": options.get("notification_url"),
|
|
779
|
+
"batch_id": options.get("batch_id")
|
|
743
780
|
}
|
|
781
|
+
options["non_signable"] = _NON_SIGNABLE_NOTIFICATION_PARAMS
|
|
744
782
|
return call_api("context", params, **options)
|
|
745
783
|
|
|
746
784
|
|
|
@@ -778,6 +816,7 @@ def text(text, **options):
|
|
|
778
816
|
|
|
779
817
|
_SLIDESHOW_PARAMS = [
|
|
780
818
|
"notification_url",
|
|
819
|
+
"batch_id",
|
|
781
820
|
"public_id",
|
|
782
821
|
"overwrite",
|
|
783
822
|
"upload_preset",
|
|
@@ -791,6 +830,8 @@ def create_slideshow(**options):
|
|
|
791
830
|
:param options: Additional parameters for the slideshow creation.
|
|
792
831
|
:keyword str resource_type: The resource type, defaults to "video".
|
|
793
832
|
:keyword str notification_url: A URL to be notified when the processing is completed.
|
|
833
|
+
:keyword str batch_id: Correlation key for the completion notification, used to address it
|
|
834
|
+
when polling.
|
|
794
835
|
:keyword str public_id: The public ID to assign to the generated slideshow.
|
|
795
836
|
:keyword bool overwrite: Whether to overwrite the slideshow if public_id already exists.
|
|
796
837
|
:keyword str upload_preset: An upload preset to apply to the slideshow creation.
|
|
@@ -629,6 +629,16 @@ def json_body(params, headers=None):
|
|
|
629
629
|
|
|
630
630
|
|
|
631
631
|
def sign_request(params, options):
|
|
632
|
+
"""
|
|
633
|
+
Signs Upload API request parameters.
|
|
634
|
+
|
|
635
|
+
Keys listed in options["non_signable"] are still sent, but left out of the signature.
|
|
636
|
+
|
|
637
|
+
:param params: Params to sign and send.
|
|
638
|
+
:param options: Additional options, including non_signable.
|
|
639
|
+
:return: The params, with signature and api_key added.
|
|
640
|
+
:internal
|
|
641
|
+
"""
|
|
632
642
|
api_key = options.get("api_key", cloudinary.config().api_key)
|
|
633
643
|
if not api_key:
|
|
634
644
|
raise ValueError("Must supply api_key")
|
|
@@ -637,9 +647,11 @@ def sign_request(params, options):
|
|
|
637
647
|
raise ValueError("Must supply api_secret")
|
|
638
648
|
signature_algorithm = options.get("signature_algorithm", cloudinary.config().signature_algorithm)
|
|
639
649
|
signature_version = options.get("signature_version", cloudinary.config().signature_version)
|
|
650
|
+
non_signable = options.get("non_signable") or ()
|
|
640
651
|
|
|
641
652
|
params = cleanup_params(params)
|
|
642
|
-
|
|
653
|
+
params_to_sign = params if not non_signable else dict((k, v) for k, v in params.items() if k not in non_signable)
|
|
654
|
+
params["signature"] = api_sign_request(params_to_sign, api_secret, signature_algorithm, signature_version)
|
|
643
655
|
params["api_key"] = api_key
|
|
644
656
|
|
|
645
657
|
return params
|
|
@@ -1254,6 +1266,7 @@ def build_multi_and_sprite_params(**options):
|
|
|
1254
1266
|
"notification_url": options.get("notification_url"),
|
|
1255
1267
|
"tag": tag,
|
|
1256
1268
|
"urls": urls,
|
|
1269
|
+
"public_id": options.get("public_id"),
|
|
1257
1270
|
"transformation": generate_transformation_string(fetch_format=options.get("format"), **options)[0]
|
|
1258
1271
|
}
|
|
1259
1272
|
return params
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cloudinary"
|
|
3
3
|
description = "Upload, transform, optimize, and manage images and videos with Cloudinary from Python or Django."
|
|
4
|
-
version = "1.46.
|
|
4
|
+
version = "1.46.3"
|
|
5
5
|
|
|
6
6
|
authors = [{ name = "Cloudinary", email = "info@cloudinary.com" }]
|
|
7
7
|
license = { file = "LICENSE" }
|
|
@@ -7,7 +7,7 @@ if version_info[0] >= 3:
|
|
|
7
7
|
else:
|
|
8
8
|
# Following code is legacy (Python 2.7 compatibility) and will be removed in the future!
|
|
9
9
|
# TODO: Remove in next major update (when dropping Python 2.7 compatibility)
|
|
10
|
-
version = "1.46.
|
|
10
|
+
version = "1.46.3"
|
|
11
11
|
|
|
12
12
|
with open('README.md') as file:
|
|
13
13
|
long_description = file.read()
|
|
@@ -522,9 +522,11 @@ class ApiTest(unittest.TestCase):
|
|
|
522
522
|
""" should allow deleting resources by transformations """
|
|
523
523
|
mocker.return_value = MOCK_RESPONSE
|
|
524
524
|
|
|
525
|
-
api.delete_resources(['api_test', 'api_test2'], transformations=['c_crop,w_100']
|
|
525
|
+
api.delete_resources(['api_test', 'api_test2'], transformations=['c_crop,w_100'],
|
|
526
|
+
notification_url="http://example.com")
|
|
526
527
|
self.assertEqual(get_method(mocker), 'DELETE')
|
|
527
528
|
self.assertEqual(get_param(mocker, 'transformations'), 'c_crop,w_100')
|
|
529
|
+
self.assertEqual(get_param(mocker, 'notification_url'), "http://example.com")
|
|
528
530
|
|
|
529
531
|
api.delete_all_resources(transformations=['c_crop,w_100', {"crop": "scale", "width": 107}])
|
|
530
532
|
self.assertEqual(get_method(mocker), 'DELETE')
|
|
@@ -824,11 +826,12 @@ class ApiTest(unittest.TestCase):
|
|
|
824
826
|
@patch(URLLIB3_REQUEST)
|
|
825
827
|
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
826
828
|
def test21_notification_url(self, mocker):
|
|
827
|
-
""" should support notification_url
|
|
829
|
+
""" should support notification_url and batch_id params """
|
|
828
830
|
mocker.return_value = MOCK_RESPONSE
|
|
829
|
-
api.update("api_test", notification_url="http://example.com")
|
|
831
|
+
api.update("api_test", notification_url="http://example.com", batch_id="batch_1")
|
|
830
832
|
notification_url = get_param(mocker, 'notification_url')
|
|
831
833
|
self.assertEqual(notification_url, "http://example.com")
|
|
834
|
+
self.assertEqual(get_param(mocker, 'batch_id'), "batch_1")
|
|
832
835
|
|
|
833
836
|
@patch(URLLIB3_REQUEST)
|
|
834
837
|
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
@@ -132,6 +132,15 @@ class AuthTokenTest(unittest.TestCase):
|
|
|
132
132
|
def test_must_provide_expiration_or_duration(self):
|
|
133
133
|
self.assertRaises(Exception, cloudinary.utils.generate_auth_token, acl="*", expiration=None, duration=None)
|
|
134
134
|
|
|
135
|
+
def test_reject_invalid_numeric_options(self):
|
|
136
|
+
for option in ("start_time", "duration", "expiration"):
|
|
137
|
+
for value in ("invalid", [300], {"seconds": 300}, complex(1, 2)):
|
|
138
|
+
options = {"acl": "*", "start_time": 1111111111, "duration": 300}
|
|
139
|
+
options[option] = value
|
|
140
|
+
with self.assertRaises(ValueError) as error:
|
|
141
|
+
cloudinary.utils.generate_auth_token(**options)
|
|
142
|
+
self.assertEqual(str(error.exception), "Value '{}' must be an integer.".format(value))
|
|
143
|
+
|
|
135
144
|
def test_must_provide_acl_or_url(self):
|
|
136
145
|
self.assertRaises(Exception, cloudinary.utils.generate_auth_token, start_time=1111111111, duration=300)
|
|
137
146
|
|
|
@@ -267,6 +267,22 @@ class AccountApiTest(unittest.TestCase):
|
|
|
267
267
|
self.assertEqual("ok", named_key_del_res["message"])
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
class UserInUserGroupsTest(unittest.TestCase):
|
|
271
|
+
"""
|
|
272
|
+
Verifies the request shape against a mocked transport, so no account credentials are needed.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
def test_user_in_user_groups(self):
|
|
276
|
+
with patch(URLLIB3_REQUEST) as mocker:
|
|
277
|
+
mocker.return_value = api_response_mock()
|
|
278
|
+
cloudinary.provisioning.user_in_user_groups("user_id", account_id="account_id",
|
|
279
|
+
provisioning_api_key="key",
|
|
280
|
+
provisioning_api_secret="secret")
|
|
281
|
+
|
|
282
|
+
self.assertEqual("GET", get_method(mocker))
|
|
283
|
+
self.assertTrue(get_uri(mocker).endswith("/provisioning/accounts/account_id/users/user_id/groups"))
|
|
284
|
+
|
|
285
|
+
|
|
270
286
|
class CreateAgentAccountTest(unittest.TestCase):
|
|
271
287
|
"""
|
|
272
288
|
The create agent account endpoint is public, unauthenticated and rate limited per IP,
|
|
@@ -386,13 +386,16 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
386
386
|
@patch(URLLIB3_REQUEST)
|
|
387
387
|
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
388
388
|
def test_rename_parameters(self, mocker):
|
|
389
|
-
"""Should support to_type, invalidate, and
|
|
389
|
+
"""Should support to_type, invalidate, overwrite, notification_url and batch_id"""
|
|
390
390
|
mocker.return_value = MOCK_RESPONSE
|
|
391
|
-
uploader.rename(TEST_IMAGE, TEST_IMAGE + "2", to_type='raw', invalidate=True, overwrite=False
|
|
391
|
+
uploader.rename(TEST_IMAGE, TEST_IMAGE + "2", to_type='raw', invalidate=True, overwrite=False,
|
|
392
|
+
notification_url="poll://*", batch_id="batch_1")
|
|
392
393
|
|
|
393
394
|
self.assertEqual(get_params(mocker)['to_type'], 'raw')
|
|
394
395
|
self.assertTrue(get_params(mocker)['invalidate'])
|
|
395
396
|
self.assertTrue(get_params(mocker)['overwrite'])
|
|
397
|
+
self.assertEqual(get_params(mocker)['notification_url'], "poll://*")
|
|
398
|
+
self.assertEqual(get_params(mocker)['batch_id'], "batch_1")
|
|
396
399
|
|
|
397
400
|
@patch(URLLIB3_REQUEST)
|
|
398
401
|
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
@@ -577,7 +580,8 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
577
580
|
}
|
|
578
581
|
},
|
|
579
582
|
transformation={"fetch_format": "auto", "quality": "auto"},
|
|
580
|
-
tags=['tag1', 'tag2', 'tag3']
|
|
583
|
+
tags=['tag1', 'tag2', 'tag3'],
|
|
584
|
+
batch_id="batch_1"
|
|
581
585
|
)
|
|
582
586
|
|
|
583
587
|
args, _ = mocker.call_args
|
|
@@ -587,6 +591,7 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
587
591
|
self.assertEqual("fn_render:" + slideshow_manifest, get_params(mocker)['manifest_transformation'])
|
|
588
592
|
self.assertEqual("f_auto,q_auto", get_params(mocker)['transformation'])
|
|
589
593
|
self.assertEqual("tag1,tag2,tag3", get_params(mocker)['tags'])
|
|
594
|
+
self.assertEqual("batch_1", get_params(mocker)['batch_id'])
|
|
590
595
|
|
|
591
596
|
@patch(URLLIB3_REQUEST)
|
|
592
597
|
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
@@ -1194,6 +1199,7 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1194
1199
|
TEST_ID,
|
|
1195
1200
|
page="all",
|
|
1196
1201
|
format="jpg",
|
|
1202
|
+
type="private",
|
|
1197
1203
|
notification_url="poll://*",
|
|
1198
1204
|
batch_id=batch_id,
|
|
1199
1205
|
)
|
|
@@ -1201,6 +1207,7 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1201
1207
|
params = get_params(request_mock)
|
|
1202
1208
|
self.assertEqual(params["public_id"], TEST_ID)
|
|
1203
1209
|
self.assertEqual(params["format"], "jpg")
|
|
1210
|
+
self.assertEqual(params["type"], "private")
|
|
1204
1211
|
self.assertEqual(params["notification_url"], "poll://*")
|
|
1205
1212
|
self.assertEqual(params["batch_id"], batch_id)
|
|
1206
1213
|
|
|
@@ -1237,6 +1244,10 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1237
1244
|
uploader.destroy(result.get("public_id"))
|
|
1238
1245
|
self.assertIn("f_jpg,w_100", result["css_url"])
|
|
1239
1246
|
|
|
1247
|
+
# Generate sprite with an explicit public_id, instead of one derived from the tag
|
|
1248
|
+
result = uploader.generate_sprite(sprite_test_tag, public_id=sprite_test_tag + "_named")
|
|
1249
|
+
self.assertIn(sprite_test_tag + "_named", result["css_url"])
|
|
1250
|
+
|
|
1240
1251
|
def test_download_sprite(self):
|
|
1241
1252
|
"""Should generate signed download url for sprite"""
|
|
1242
1253
|
sprite_test_tag = "sprite_tag"
|
|
@@ -1286,13 +1297,17 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1286
1297
|
self.assertTrue(pdf_result.get("url").endswith(".pdf"))
|
|
1287
1298
|
self.assertIn("w_111", pdf_result.get("url"))
|
|
1288
1299
|
|
|
1300
|
+
# Generate multi with an explicit public_id, instead of one derived from the tag
|
|
1301
|
+
named_result = uploader.multi(tag=multi_test_tag, public_id=multi_test_tag + "_named")
|
|
1302
|
+
self.assertEqual(multi_test_tag + "_named", named_result.get("public_id"))
|
|
1303
|
+
|
|
1289
1304
|
def test_download_multi(self):
|
|
1290
1305
|
"""Should generate signed download url for multi"""
|
|
1291
1306
|
multi_test_tag = "multi_test_tag"
|
|
1292
1307
|
url_1 = "https://res.cloudinary.com/demo/image/upload/sample"
|
|
1293
1308
|
url_2 = "https://res.cloudinary.com/demo/image/upload/car"
|
|
1294
1309
|
|
|
1295
|
-
url_from_tag = uploader.download_multi(tag=multi_test_tag)
|
|
1310
|
+
url_from_tag = uploader.download_multi(tag=multi_test_tag, public_id=TEST_ID)
|
|
1296
1311
|
url_from_urls = uploader.download_multi(urls=[url_1, url_2])
|
|
1297
1312
|
|
|
1298
1313
|
self.assertTrue(url_from_tag.startswith(
|
|
@@ -1302,6 +1317,7 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1302
1317
|
|
|
1303
1318
|
parameters = parse_qs(urlparse(url_from_tag).query)
|
|
1304
1319
|
self.assertEqual(multi_test_tag, parameters["tag"][0])
|
|
1320
|
+
self.assertEqual(TEST_ID, parameters["public_id"][0])
|
|
1305
1321
|
self.assertEqual("download", parameters["mode"][0])
|
|
1306
1322
|
self.assertIn("timestamp", parameters)
|
|
1307
1323
|
self.assertIn("signature", parameters)
|
|
@@ -1321,6 +1337,54 @@ P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\
|
|
|
1321
1337
|
uploader.create_zip(tags="test-tag", target_asset_folder="test-asset-folder")
|
|
1322
1338
|
self.assertEqual("test-asset-folder", get_param(mocker, "target_asset_folder"))
|
|
1323
1339
|
|
|
1340
|
+
@staticmethod
|
|
1341
|
+
def sign_request_without(params, *excluded):
|
|
1342
|
+
"""
|
|
1343
|
+
Signs the params as sent in the request, leaving out the excluded keys.
|
|
1344
|
+
|
|
1345
|
+
:param params: The params as sent in the request.
|
|
1346
|
+
:param excluded: Keys to leave out of the signature.
|
|
1347
|
+
:return: The expected signature.
|
|
1348
|
+
"""
|
|
1349
|
+
config = cloudinary.config()
|
|
1350
|
+
params_to_sign = dict((k, v) for k, v in params.items()
|
|
1351
|
+
if k not in excluded and k not in ("signature", "api_key"))
|
|
1352
|
+
return utils.api_sign_request(params_to_sign, config.api_secret, config.signature_algorithm,
|
|
1353
|
+
config.signature_version)
|
|
1354
|
+
|
|
1355
|
+
def assert_notification_params_unsigned(self, mocker):
|
|
1356
|
+
"""
|
|
1357
|
+
Assert notification_url and batch_id were sent, but left out of the signature.
|
|
1358
|
+
|
|
1359
|
+
:param mocker: The mocked request.
|
|
1360
|
+
"""
|
|
1361
|
+
params = get_params(mocker)
|
|
1362
|
+
self.assertEqual("poll://*", params["notification_url"])
|
|
1363
|
+
self.assertEqual("batch_1", params["batch_id"])
|
|
1364
|
+
self.assertEqual(self.sign_request_without(params, "notification_url", "batch_id"), params["signature"])
|
|
1365
|
+
|
|
1366
|
+
@patch(URLLIB3_REQUEST)
|
|
1367
|
+
@unittest.skipUnless(cloudinary.config().api_secret, "requires api_key/api_secret")
|
|
1368
|
+
def test_notification_params_signing(self, mocker):
|
|
1369
|
+
"""Should send notification_url and batch_id, unsigned on tags, context and metadata"""
|
|
1370
|
+
mocker.return_value = MOCK_RESPONSE
|
|
1371
|
+
options = {"notification_url": "poll://*", "batch_id": "batch_1"}
|
|
1372
|
+
|
|
1373
|
+
uploader.add_tag("tag1", [TEST_ID], **options)
|
|
1374
|
+
self.assert_notification_params_unsigned(mocker)
|
|
1375
|
+
|
|
1376
|
+
uploader.add_context({"k": "v"}, [TEST_ID], **options)
|
|
1377
|
+
self.assert_notification_params_unsigned(mocker)
|
|
1378
|
+
|
|
1379
|
+
uploader.update_metadata({"k": "v"}, [TEST_ID], **options)
|
|
1380
|
+
self.assert_notification_params_unsigned(mocker)
|
|
1381
|
+
|
|
1382
|
+
uploader.destroy(TEST_ID, **options)
|
|
1383
|
+
params = get_params(mocker)
|
|
1384
|
+
self.assertEqual("poll://*", params["notification_url"])
|
|
1385
|
+
self.assertEqual("batch_1", params["batch_id"])
|
|
1386
|
+
self.assertEqual(self.sign_request_without(params), params["signature"])
|
|
1387
|
+
|
|
1324
1388
|
|
|
1325
1389
|
if __name__ == '__main__':
|
|
1326
1390
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/cache/storage/file_system_key_value_storage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/html/cloudinary_cors.html
RENAMED
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/canvas-to-blob.min.js
RENAMED
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.cloudinary.js
RENAMED
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload-image.js
RENAMED
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload-process.js
RENAMED
|
File without changes
|
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.fileupload.js
RENAMED
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/jquery.iframe-transport.js
RENAMED
|
File without changes
|
|
File without changes
|
{cloudinary-1.46.1 → cloudinary-1.46.3}/cloudinary/static/cloudinary/js/load-image.all.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|