diff --git a/DOCKER.md b/DOCKER.md index f1b3a41..a95bd6d 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -250,6 +250,9 @@ docker run --rm -e CLOUDINARY_URL cloudinary-cli uploader -ls # List all admin methods docker run --rm -e CLOUDINARY_URL cloudinary-cli admin -ls +# List all collections methods +docker run --rm -e CLOUDINARY_URL cloudinary-cli collections -ls + # Get help for specific commands docker run --rm -e CLOUDINARY_URL cloudinary-cli uploader --help docker run --rm -e CLOUDINARY_URL cloudinary-cli search --help diff --git a/README.md b/README.md index 48d5ef2..ecec0b9 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ cld logout # Revokes and removes a saved OAuth login. cld search --help # Shows usage for the Search API. cld admin # Lists Admin API methods. cld uploader # Lists Upload API methods. +cld collections # Lists Collections API methods. cld agent signup # For AI agents: creates a Cloudinary account on behalf of a human. cld agent cloud # For AI agents: creates a temporary cloud that works immediately. ``` @@ -236,6 +237,32 @@ cld admin transformation my_new_transformation _**Note:** you can omit 'admin' from the command when calling an Admin API method._ +## Collections API + +Enables you to run any methods that can be called through the collections API, to organize assets into collections and share them through public pages. + +You can find documentation for each of the Collections API methods at [https://cloudinary.com/documentation/collections_api](https://cloudinary.com/documentation/collections_api). + +The basic syntax using the Collections API is as follows: + +``` +cld [cli options] collections [command options] [method] [method parameters] +``` + +Example: create a collection, add an asset to it and publish it as a public page: + +``` +cld collections create_collection "Summer campaign" -o description "Campaign assets" +cld collections add_collection_assets '[""]' +cld collections create_collection_public_page +``` + +Create a dynamic collection from a search expression: + +``` +cld collections create_collection "Summer images" -o type dynamic -O configuration '{"expression": "tags=summer"}' +``` + ## Search API Runs the admin API search method, allowing you to use a Lucene query string as the expression. diff --git a/cloudinary_cli/core/__init__.py b/cloudinary_cli/core/__init__.py index d2d5df3..b2c59cf 100644 --- a/cloudinary_cli/core/__init__.py +++ b/cloudinary_cli/core/__init__.py @@ -3,6 +3,7 @@ from cloudinary_cli.core.admin import admin from cloudinary_cli.core.agent import agent_group from cloudinary_cli.core.auth import login, logout +from cloudinary_cli.core.collections import collections from cloudinary_cli.core.config import config_command from cloudinary_cli.core.search import search, search_folders from cloudinary_cli.core.uploader import uploader @@ -22,6 +23,7 @@ admin, uploader, provisioning, + collections, url, utils ] diff --git a/cloudinary_cli/core/collections.py b/cloudinary_cli/core/collections.py new file mode 100644 index 0000000..33b24a3 --- /dev/null +++ b/cloudinary_cli/core/collections.py @@ -0,0 +1,29 @@ +from click import command, argument, option +import cloudinary.collections + +from cloudinary_cli.utils.api_utils import handle_api_command + + +@command("collections", + short_help="Run any methods that can be called through the collections API.", + help="""\b +Run any methods that can be called through the collections API. +Format: cld collections +\te.g. cld collections create_collection "Summer campaign" -o description "Campaign assets" +\t OR +\t cld collections add_collection_assets '[""]' +""") +@argument("params", nargs=-1) +@option("-o", "--optional_parameter", multiple=True, nargs=2, + help="Pass an optional parameter as a string, with no parsing. e.g. -o query summer") +@option("-O", "--optional_parameter_parsed", multiple=True, nargs=2, + help="Pass an optional parameter and parse its value as JSON or a boolean. " + "e.g. -O configuration '{\"expression\": \"tags=summer\"}'") +@option("-ls", "--ls", is_flag=True, help="List all available methods in the Collections API.") +@option("--save", nargs=1, help="Save output to a file.") +@option("-d", "--doc", is_flag=True, help="Open the Collections API reference in a browser.") +def collections(params, optional_parameter, optional_parameter_parsed, ls, save, doc): + return handle_api_command(params, optional_parameter, optional_parameter_parsed, ls, save, doc, + doc_url="https://cloudinary.com/documentation/collections_api", + api_instance=cloudinary.collections, + api_name="collections") diff --git a/cloudinary_cli/utils/utils.py b/cloudinary_cli/utils/utils.py index 790a014..7643df5 100644 --- a/cloudinary_cli/utils/utils.py +++ b/cloudinary_cli/utils/utils.py @@ -22,7 +22,8 @@ not_callable = ('is_appengine_sandbox', 'call_tags_api', 'call_context_api', 'call_cacheable_api', 'call_api', 'call_metadata_api', 'call_json_api', 'only', 'transformation_string', 'account_config', - 'reset_config', 'upload_large_part', 'upload_image', 'upload_resource', 'build_eager') + 'reset_config', 'upload_large_part', 'upload_image', 'upload_resource', 'build_eager', + 'base64url_decode') BLOCK_SIZE = 65536 diff --git a/requirements.txt b/requirements.txt index 8c4d509..691424b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cloudinary>=1.46.3 +cloudinary>=1.47.0 pygments jinja2 click diff --git a/test/test_cli.py b/test/test_cli.py index 48e7ac3..016c744 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -16,6 +16,7 @@ class TestCLI(unittest.TestCase): COMMANDS = [ 'admin', 'agent', + 'collections', 'config', 'login', 'logout', diff --git a/test/test_cli_api.py b/test/test_cli_api.py index c565915..4ef475c 100644 --- a/test/test_cli_api.py +++ b/test/test_cli_api.py @@ -45,6 +45,23 @@ def test_provisioning(self, mocker): self.assertEqual(0, result.exit_code, result.output) self.assertIn('"foo": "bar"', result.output) + @unittest.skipUnless(CONFIG_PRESENT, REQUIRES_CONFIG) + @patch(URLLIB3_REQUEST) + def test_collections(self, mocker): + mocker.return_value = API_MOCK_RESPONSE + result = self.runner.invoke(cli, ['collections', 'add_collection_assets', 'col1', '["a1", "a2"]']) + + self.assertEqual(0, result.exit_code, result.output) + self.assertIn('"foo": "bar"', result.output) + self.assertIn('/collections/col1/assets', mocker.call_args[1]['url']) + + def test_collections_ls(self): + result = self.runner.invoke(cli, ['collections', '-ls']) + + self.assertEqual(0, result.exit_code, result.output) + self.assertIn('create_collection', result.output) + self.assertNotIn('base64url_decode', result.output) + class TestDestructiveBulkConfirmation(unittest.TestCase): runner = CliRunner()