1# Copyright Cloudinary 2 3import datetime 4import email.utils 5import json 6import socket 7 8import urllib3 9from six import string_types 10from urllib3.exceptions import HTTPError 11 12import cloudinary 13from cloudinary import utils 14from cloudinary.api_client.call_api import ( 15 call_api, 16 call_metadata_api, 17 call_json_api 18) 19from cloudinary.exceptions import ( 20 BadRequest, 21 AuthorizationRequired, 22 NotAllowed, 23 NotFound, 24 AlreadyExists, 25 RateLimited, 26 GeneralError 27) 28 29 30def ping(**options): 31 return call_api("get", ["ping"], {}, **options) 32 33 34def usage(**options): 35 """Get account usage details. 36 37 Get a report on the status of your Cloudinary account usage details, including storage, credits, bandwidth, 38 requests, number of resources, and add-on usage. Note that numbers are updated periodically. 39 40 See: `Get account usage details 41 <https://cloudinary.com/documentation/admin_api#get_account_usage_details>`_ 42 43 :param options: Additional options 44 :type options: dict, optional 45 :return: Detailed usage information 46 :rtype: Response 47 """ 48 date = options.pop("date", None) 49 uri = ["usage"] 50 if date: 51 if isinstance(date, datetime.date): 52 date = utils.encode_date_to_usage_api_format(date) 53 uri.append(date) 54 return call_api("get", uri, {}, **options) 55 56 57def resource_types(**options): 58 return call_api("get", ["resources"], {}, **options) 59 60 61def resources(**options): 62 resource_type = options.pop("resource_type", "image") 63 upload_type = options.pop("type", None) 64 uri = ["resources", resource_type] 65 if upload_type: 66 uri.append(upload_type) 67 params = only(options, "next_cursor", "max_results", "prefix", "tags", 68 "context", "moderations", "direction", "start_at", "metadata") 69 return call_api("get", uri, params, **options) 70 71 72def resources_by_tag(tag, **options): 73 resource_type = options.pop("resource_type", "image") 74 uri = ["resources", resource_type, "tags", tag] 75 params = only(options, "next_cursor", "max_results", "tags", 76 "context", "moderations", "direction", "metadata") 77 return call_api("get", uri, params, **options) 78 79 80def resources_by_moderation(kind, status, **options): 81 resource_type = options.pop("resource_type", "image") 82 uri = ["resources", resource_type, "moderations", kind, status] 83 params = only(options, "next_cursor", "max_results", "tags", 84 "context", "moderations", "direction", "metadata") 85 return call_api("get", uri, params, **options) 86 87 88def resources_by_ids(public_ids, **options): 89 resource_type = options.pop("resource_type", "image") 90 upload_type = options.pop("type", "upload") 91 uri = ["resources", resource_type, upload_type] 92 params = dict(only(options, "tags", "moderations", "context"), public_ids=public_ids) 93 return call_api("get", uri, params, **options) 94 95 96def resources_by_asset_ids(asset_ids, **options): 97 """Retrieves the resources (assets) indicated in the asset IDs. 98 This method does not return deleted assets even if they have been backed up. 99 100 See: `Get resources by context API reference 101 <https://cloudinary.com/documentation/admin_api#get_resources>`_ 102 103 :param asset_ids: The requested asset IDs. 104 :type asset_ids: list[str] 105 :param options: Additional options 106 :type options: dict, optional 107 :return: Resources (assets) as indicated in the asset IDs 108 :rtype: Response 109 """ 110 uri = ["resources", 'by_asset_ids'] 111 params = dict(only(options, "tags", "moderations", "context"), asset_ids=asset_ids) 112 return call_api("get", uri, params, **options) 113 114 115def resources_by_context(key, value=None, **options): 116 """Retrieves resources (assets) with a specified context key. 117 This method does not return deleted assets even if they have been backed up. 118 119 See: `Get resources by context API reference 120 <https://cloudinary.com/documentation/admin_api#get_resources_by_context>`_ 121 122 :param key: Only assets with this context key are returned 123 :type key: str 124 :param value: Only assets with this value for the context key are returned 125 :type value: str, optional 126 :param options: Additional options 127 :type options: dict, optional 128 :return: Resources (assets) with a specified context key 129 :rtype: Response 130 """ 131 resource_type = options.pop("resource_type", "image") 132 uri = ["resources", resource_type, "context"] 133 params = only(options, "next_cursor", "max_results", "tags", 134 "context", "moderations", "direction", "metadata") 135 params["key"] = key 136 if value is not None: 137 params["value"] = value 138 return call_api("get", uri, params, **options) 139 140 141def resource(public_id, **options): 142 resource_type = options.pop("resource_type", "image") 143 upload_type = options.pop("type", "upload") 144 uri = ["resources", resource_type, upload_type, public_id] 145 params = _prepare_asset_details_params(**options) 146 return call_api("get", uri, params, **options) 147 148 149def resource_by_asset_id(asset_id, **options): 150 """ 151 Returns the details of the specified asset and all its derived assets by asset id. 152 153 :param asset_id: The Asset ID of the asset 154 :type asset_id: string 155 :param options: Additional options 156 :type options: dict, optional 157 :return: Resource (asset) of a specific asset_id 158 :rtype: Response 159 """ 160 uri = ["resources", asset_id] 161 params = _prepare_asset_details_params(**options) 162 return call_api("get", uri, params, **options) 163 164 165def _prepare_asset_details_params(**options): 166 """ 167 Prepares optional parameters for resource_by_asset_id API calls. 168 169 :param options: Additional options 170 :return: Optional parameters 171 172 :internal 173 """ 174 return only(options, "exif", "faces", "colors", "image_metadata", "cinemagraph_analysis", 175 "pages", "phash", "coordinates", "max_results", "quality_analysis", "derived_next_cursor", 176 "accessibility_analysis", "versions") 177 178 179def update(public_id, **options): 180 resource_type = options.pop("resource_type", "image") 181 upload_type = options.pop("type", "upload") 182 uri = ["resources", resource_type, upload_type, public_id] 183 params = only(options, "moderation_status", "raw_convert", 184 "quality_override", "ocr", 185 "categorization", "detection", "similarity_search", 186 "background_removal", "notification_url") 187 if "tags" in options: 188 params["tags"] = ",".join(utils.build_array(options["tags"])) 189 if "face_coordinates" in options: 190 params["face_coordinates"] = utils.encode_double_array( 191 options.get("face_coordinates")) 192 if "custom_coordinates" in options: 193 params["custom_coordinates"] = utils.encode_double_array( 194 options.get("custom_coordinates")) 195 if "context" in options: 196 params["context"] = utils.encode_context(options.get("context")) 197 if "auto_tagging" in options: 198 params["auto_tagging"] = str(options.get("auto_tagging")) 199 if "access_control" in options: 200 params["access_control"] = utils.json_encode(utils.build_list_of_dicts(options.get("access_control"))) 201 202 return call_api("post", uri, params, **options) 203 204 205def delete_resources(public_ids, **options): 206 resource_type = options.pop("resource_type", "image") 207 upload_type = options.pop("type", "upload") 208 uri = ["resources", resource_type, upload_type] 209 params = __delete_resource_params(options, public_ids=public_ids) 210 return call_api("delete", uri, params, **options) 211 212 213def delete_resources_by_prefix(prefix, **options): 214 resource_type = options.pop("resource_type", "image") 215 upload_type = options.pop("type", "upload") 216 uri = ["resources", resource_type, upload_type] 217 params = __delete_resource_params(options, prefix=prefix) 218 return call_api("delete", uri, params, **options) 219 220 221def delete_all_resources(**options): 222 resource_type = options.pop("resource_type", "image") 223 upload_type = options.pop("type", "upload") 224 uri = ["resources", resource_type, upload_type] 225 params = __delete_resource_params(options, all=True) 226 return call_api("delete", uri, params, **options) 227 228 229def delete_resources_by_tag(tag, **options): 230 resource_type = options.pop("resource_type", "image") 231 uri = ["resources", resource_type, "tags", tag] 232 params = __delete_resource_params(options) 233 return call_api("delete", uri, params, **options) 234 235 236def delete_derived_resources(derived_resource_ids, **options): 237 uri = ["derived_resources"] 238 params = {"derived_resource_ids": derived_resource_ids} 239 return call_api("delete", uri, params, **options) 240 241 242def delete_derived_by_transformation(public_ids, transformations, 243 resource_type='image', type='upload', invalidate=None, 244 **options): 245 """Delete derived resources of public ids, identified by transformations 246 247 :param public_ids: the base resources 248 :type public_ids: list of str 249 :param transformations: the transformation of derived resources, optionally including the format 250 :type transformations: list of (dict or str) 251 :param type: The upload type 252 :type type: str 253 :param resource_type: The type of the resource: defaults to "image" 254 :type resource_type: str 255 :param invalidate: (optional) True to invalidate the resources after deletion 256 :type invalidate: bool 257 :return: a list of the public ids for which derived resources were deleted 258 :rtype: dict 259 """ 260 uri = ["resources", resource_type, type] 261 if not isinstance(public_ids, list): 262 public_ids = [public_ids] 263 params = {"public_ids": public_ids, 264 "transformations": utils.build_eager(transformations), 265 "keep_original": True} 266 if invalidate is not None: 267 params['invalidate'] = invalidate 268 return call_api("delete", uri, params, **options) 269 270 271def tags(**options): 272 resource_type = options.pop("resource_type", "image") 273 uri = ["tags", resource_type] 274 return call_api("get", uri, only(options, "next_cursor", "max_results", "prefix"), **options) 275 276 277def transformations(**options): 278 uri = ["transformations"] 279 params = only(options, "named", "next_cursor", "max_results") 280 281 return call_api("get", uri, params, **options) 282 283 284def transformation(transformation, **options): 285 uri = ["transformations"] 286 287 params = only(options, "next_cursor", "max_results") 288 params["transformation"] = utils.build_single_eager(transformation) 289 290 return call_api("get", uri, params, **options) 291 292 293def delete_transformation(transformation, **options): 294 uri = ["transformations"] 295 296 params = {"transformation": utils.build_single_eager(transformation)} 297 298 return call_api("delete", uri, params, **options) 299 300 301# updates - currently only supported update is the "allowed_for_strict" 302# boolean flag and unsafe_update 303def update_transformation(transformation, **options): 304 uri = ["transformations"] 305 306 updates = only(options, "allowed_for_strict") 307 308 if "unsafe_update" in options: 309 updates["unsafe_update"] = transformation_string(options.get("unsafe_update")) 310 311 updates["transformation"] = utils.build_single_eager(transformation) 312 313 return call_api("put", uri, updates, **options) 314 315 316def create_transformation(name, definition, **options): 317 uri = ["transformations"] 318 319 params = {"name": name, "transformation": utils.build_single_eager(definition)} 320 321 return call_api("post", uri, params, **options) 322 323 324def publish_by_ids(public_ids, **options): 325 resource_type = options.pop("resource_type", "image") 326 uri = ["resources", resource_type, "publish_resources"] 327 params = dict(only(options, "type", "overwrite", "invalidate"), public_ids=public_ids) 328 return call_api("post", uri, params, **options) 329 330 331def publish_by_prefix(prefix, **options): 332 resource_type = options.pop("resource_type", "image") 333 uri = ["resources", resource_type, "publish_resources"] 334 params = dict(only(options, "type", "overwrite", "invalidate"), prefix=prefix) 335 return call_api("post", uri, params, **options) 336 337 338def publish_by_tag(tag, **options): 339 resource_type = options.pop("resource_type", "image") 340 uri = ["resources", resource_type, "publish_resources"] 341 params = dict(only(options, "type", "overwrite", "invalidate"), tag=tag) 342 return call_api("post", uri, params, **options) 343 344 345def upload_presets(**options): 346 uri = ["upload_presets"] 347 return call_api("get", uri, only(options, "next_cursor", "max_results"), **options) 348 349 350def upload_preset(name, **options): 351 uri = ["upload_presets", name] 352 return call_api("get", uri, only(options, "max_results"), **options) 353 354 355def delete_upload_preset(name, **options): 356 uri = ["upload_presets", name] 357 return call_api("delete", uri, {}, **options) 358 359 360def update_upload_preset(name, **options): 361 uri = ["upload_presets", name] 362 params = utils.build_upload_params(**options) 363 params = utils.cleanup_params(params) 364 params.update(only(options, "unsigned", "disallow_public_id", "live")) 365 return call_api("put", uri, params, **options) 366 367 368def create_upload_preset(**options): 369 uri = ["upload_presets"] 370 params = utils.build_upload_params(**options) 371 params = utils.cleanup_params(params) 372 params.update(only(options, "unsigned", "disallow_public_id", "name", "live")) 373 return call_api("post", uri, params, **options) 374 375 376def create_folder(path, **options): 377 return call_api("post", ["folders", path], {}, **options) 378 379 380def root_folders(**options): 381 return call_api("get", ["folders"], only(options, "next_cursor", "max_results"), **options) 382 383 384def subfolders(of_folder_path, **options): 385 return call_api("get", ["folders", of_folder_path], only(options, "next_cursor", "max_results"), **options) 386 387 388def delete_folder(path, **options): 389 """Deletes folder 390 391 Deleted folder must be empty, but can have descendant empty sub folders 392 393 :param path: The folder to delete 394 :param options: Additional options 395 396 :rtype: Response 397 """ 398 return call_api("delete", ["folders", path], {}, **options) 399 400 401def restore(public_ids, **options): 402 resource_type = options.pop("resource_type", "image") 403 upload_type = options.pop("type", "upload") 404 uri = ["resources", resource_type, upload_type, "restore"] 405 params = dict(public_ids=public_ids, **only(options, "versions")) 406 return call_json_api("post", uri, params, **options) 407 408 409def upload_mappings(**options): 410 uri = ["upload_mappings"] 411 return call_api("get", uri, only(options, "next_cursor", "max_results"), **options) 412 413 414def upload_mapping(name, **options): 415 uri = ["upload_mappings"] 416 params = dict(folder=name) 417 return call_api("get", uri, params, **options) 418 419 420def delete_upload_mapping(name, **options): 421 uri = ["upload_mappings"] 422 params = dict(folder=name) 423 return call_api("delete", uri, params, **options) 424 425 426def update_upload_mapping(name, **options): 427 uri = ["upload_mappings"] 428 params = dict(folder=name) 429 params.update(only(options, "template")) 430 return call_api("put", uri, params, **options) 431 432 433def create_upload_mapping(name, **options): 434 uri = ["upload_mappings"] 435 params = dict(folder=name) 436 params.update(only(options, "template")) 437 return call_api("post", uri, params, **options) 438 439 440def list_streaming_profiles(**options): 441 uri = ["streaming_profiles"] 442 return call_api('GET', uri, {}, **options) 443 444 445def get_streaming_profile(name, **options): 446 uri = ["streaming_profiles", name] 447 return call_api('GET', uri, {}, **options) 448 449 450def delete_streaming_profile(name, **options): 451 uri = ["streaming_profiles", name] 452 return call_api('DELETE', uri, {}, **options) 453 454 455def create_streaming_profile(name, **options): 456 uri = ["streaming_profiles"] 457 params = __prepare_streaming_profile_params(**options) 458 params["name"] = name 459 return call_api('POST', uri, params, **options) 460 461 462def update_streaming_profile(name, **options): 463 uri = ["streaming_profiles", name] 464 params = __prepare_streaming_profile_params(**options) 465 return call_api('PUT', uri, params, **options) 466 467 468def only(source, *keys): 469 return {key: source[key] for key in keys if key in source} 470 471 472def transformation_string(transformation): 473 if isinstance(transformation, string_types): 474 return transformation 475 else: 476 return cloudinary.utils.generate_transformation_string(**transformation)[0] 477 478 479def __prepare_streaming_profile_params(**options): 480 params = only(options, "display_name") 481 if "representations" in options: 482 representations = [{"transformation": transformation_string(trans)} 483 for trans in options["representations"]] 484 params["representations"] = json.dumps(representations) 485 return params 486 487 488def __delete_resource_params(options, **params): 489 p = dict(transformations=utils.build_eager(options.get('transformations')), 490 **only(options, "keep_original", "next_cursor", "invalidate")) 491 p.update(params) 492 return p 493 494 495def list_metadata_fields(**options): 496 """Returns a list of all metadata field definitions 497 498 See: `Get metadata fields API reference <https://cloudinary.com/documentation/admin_api#get_metadata_fields>`_ 499 500 :param options: Additional options 501 502 :rtype: Response 503 """ 504 return call_metadata_api("get", [], {}, **options) 505 506 507def metadata_field_by_field_id(field_external_id, **options): 508 """Gets a metadata field by external id 509 510 See: `Get metadata field by external ID API reference 511 <https://cloudinary.com/documentation/admin_api#get_a_metadata_field_by_external_id>`_ 512 513 :param field_external_id: The ID of the metadata field to retrieve 514 :param options: Additional options 515 516 :rtype: Response 517 """ 518 uri = [field_external_id] 519 return call_metadata_api("get", uri, {}, **options) 520 521 522def add_metadata_field(field, **options): 523 """Creates a new metadata field definition 524 525 See: `Create metadata field API reference <https://cloudinary.com/documentation/admin_api#create_a_metadata_field>`_ 526 527 :param field: The field to add 528 :param options: Additional options 529 530 :rtype: Response 531 """ 532 params = only(field, "type", "external_id", "label", "mandatory", 533 "default_value", "validation", "datasource") 534 return call_metadata_api("post", [], params, **options) 535 536 537def update_metadata_field(field_external_id, field, **options): 538 """Updates a metadata field by external id 539 540 Updates a metadata field definition (partially, no need to pass the entire 541 object) passed as JSON data. 542 543 See `Generic structure of a metadata field 544 <https://cloudinary.com/documentation/admin_api#generic_structure_of_a_metadata_field>`_ for details. 545 546 :param field_external_id: The id of the metadata field to update 547 :param field: The field definition 548 :param options: Additional options 549 550 :rtype: Response 551 """ 552 uri = [field_external_id] 553 params = only(field, "label", "mandatory", "default_value", "validation") 554 return call_metadata_api("put", uri, params, **options) 555 556 557def delete_metadata_field(field_external_id, **options): 558 """Deletes a metadata field definition. 559 The field should no longer be considered a valid candidate for all other endpoints 560 561 See: `Delete metadata field API reference 562 <https://cloudinary.com/documentation/admin_api#delete_a_metadata_field_by_external_id>`_ 563 564 :param field_external_id: The external id of the field to delete 565 :param options: Additional options 566 567 :return: An array with a "message" key. "ok" value indicates a successful deletion. 568 :rtype: Response 569 """ 570 uri = [field_external_id] 571 return call_metadata_api("delete", uri, {}, **options) 572 573 574def delete_datasource_entries(field_external_id, entries_external_id, **options): 575 """Deletes entries in a metadata field datasource 576 577 Deletes (blocks) the datasource entries for a specified metadata field 578 definition. Sets the state of the entries to inactive. This is a soft delete, 579 the entries still exist under the hood and can be activated again with the 580 restore datasource entries method. 581 582 See: `Delete entries in a metadata field datasource API reference 583 <https://cloudinary.com/documentation/admin_api#delete_entries_in_a_metadata_field_datasource>`_ 584 585 :param field_external_id: The id of the field to update 586 :param entries_external_id: The ids of all the entries to delete from the 587 datasource 588 :param options: Additional options 589 590 :rtype: Response 591 """ 592 uri = [field_external_id, "datasource"] 593 params = {"external_ids": entries_external_id} 594 return call_metadata_api("delete", uri, params, **options) 595 596 597def update_metadata_field_datasource(field_external_id, entries_external_id, **options): 598 """Updates a metadata field datasource 599 600 Updates the datasource of a supported field type (currently only enum and set), 601 passed as JSON data. The update is partial: datasource entries with an 602 existing external_id will be updated and entries with new external_id's (or 603 without external_id's) will be appended. 604 605 See: `Update a metadata field datasource API reference 606 <https://cloudinary.com/documentation/admin_api#update_a_metadata_field_datasource>`_ 607 608 :param field_external_id: The external id of the field to update 609 :param entries_external_id: 610 :param options: Additional options 611 612 :rtype: Response 613 """ 614 values = [] 615 for item in entries_external_id: 616 external = only(item, "external_id", "value") 617 if external: 618 values.append(external) 619 620 uri = [field_external_id, "datasource"] 621 params = {"values": values} 622 return call_metadata_api("put", uri, params, **options) 623 624 625def restore_metadata_field_datasource(field_external_id, entries_external_ids, **options): 626 """Restores entries in a metadata field datasource 627 628 Restores (unblocks) any previously deleted datasource entries for a specified 629 metadata field definition. 630 Sets the state of the entries to active. 631 632 See: `Restore entries in a metadata field datasource API reference 633 <https://cloudinary.com/documentation/admin_api#restore_entries_in_a_metadata_field_datasource>`_ 634 635 :param field_external_id: The ID of the metadata field 636 :param entries_external_ids: An array of IDs of datasource entries to restore 637 (unblock) 638 :param options: Additional options 639 640 :rtype: Response 641 """ 642 uri = [field_external_id, 'datasource_restore'] 643 params = {"external_ids": entries_external_ids} 644 return call_metadata_api("post", uri, params, **options) 645 646 647def reorder_metadata_field_datasource(field_external_id, order_by, direction=None, **options): 648 """Reorders metadata field datasource. Currently, supports only value. 649 650 :param field_external_id: The ID of the metadata field. 651 :param order_by: Criteria for the order. Currently, supports only value. 652 :param direction: Optional (gets either asc or desc). 653 :param options: Additional options. 654 655 :rtype: Response 656 """ 657 uri = [field_external_id, 'datasource', 'order'] 658 params = {'order_by': order_by, 'direction': direction} 659 return call_metadata_api('post', uri, params, **options) 660 661 662def reorder_metadata_fields(order_by, direction=None, **options): 663 """Reorders metadata fields. 664 665 :param order_by: Criteria for the order (one of the fields 'label', 'external_id', 'created_at'). 666 :param direction: Optional (gets either asc or desc). 667 :param options: Additional options. 668 669 :rtype: Response 670 """ 671 uri = ['order'] 672 params = {'order_by': order_by, 'direction': direction} 673 return call_metadata_api('put', uri, params, **options) 674