Skip to content

Commit 0646830

Browse files
Improve error handling of CLI commands
1 parent 7fed0ba commit 0646830

File tree

7 files changed

+122
-66
lines changed

7 files changed

+122
-66
lines changed

cloudinary_cli/cli.py

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,14 @@
11
#!/usr/bin/env python3
2-
import platform
3-
import shutil
42
import sys
53

6-
import click
7-
import click_log
8-
import cloudinary
4+
from click import ClickException
95

106
import cloudinary_cli.core
117
import cloudinary_cli.modules
128
import cloudinary_cli.samples
13-
from cloudinary_cli.defaults import logger
14-
from cloudinary_cli.utils.config_utils import initialize, load_config, refresh_cloudinary_config, \
15-
is_valid_cloudinary_config
9+
from cloudinary_cli.cli_group import cli
10+
from cloudinary_cli.utils.config_utils import initialize
1611
from cloudinary_cli.utils.utils import log_exception, ConfigurationError
17-
from cloudinary_cli.version import __version__ as cli_version
18-
19-
CONTEXT_SETTINGS = dict(max_content_width=shutil.get_terminal_size()[0], terminal_width=shutil.get_terminal_size()[0])
20-
21-
22-
@click.group(context_settings=CONTEXT_SETTINGS)
23-
@click.help_option()
24-
@click.version_option(cli_version, prog_name="Cloudinary CLI",
25-
message=f"%(prog)s, version %(version)s\n"
26-
f"Cloudinary SDK, version {cloudinary.VERSION}\n"
27-
f"Python, version {platform.python_version()}")
28-
@click.option("-c", "--config",
29-
help="""Tell the CLI which account to run the command on by specifying an account environment variable."""
30-
)
31-
@click.option("-C", "--config_saved",
32-
help="""Tell the CLI which account to run the command on by specifying a saved configuration - see
33-
`config` command.""")
34-
@click_log.simple_verbosity_option(logger)
35-
def cli(config, config_saved):
36-
if config:
37-
refresh_cloudinary_config(config)
38-
elif config_saved:
39-
config = load_config()
40-
if config_saved not in config:
41-
raise Exception(f"Config {config_saved} does not exist")
42-
43-
refresh_cloudinary_config(config[config_saved])
44-
45-
if not is_valid_cloudinary_config():
46-
logger.warning("No Cloudinary configuration found.")
47-
48-
return 0
4912

5013

5114
def import_commands(*command_modules):
@@ -62,21 +25,26 @@ def import_commands(*command_modules):
6225

6326

6427
def main():
28+
exit_status = 1 # very optimistic :)
29+
6530
initialize()
31+
6632
try:
67-
exit_status = cli()
33+
# we don't use standalone mode to get the return value from the command execution
34+
exit_status = cli.main(standalone_mode=False)
35+
except ClickException as e:
36+
# show usage with error message
37+
e.show()
6838
except ConfigurationError as e:
6939
log_exception(e)
70-
exit_status = 1
7140
except Exception as e:
7241
# Improve configuration error handling
7342
if "Must supply cloud_name" in str(e):
7443
log_exception("No Cloudinary configuration found.")
7544
else:
7645
log_exception(e, "Command execution failed")
77-
exit_status = 1
7846

79-
return exit_status
47+
return 0 if exit_status or exit_status is None else 1
8048

8149

8250
if __name__ == "__main__":

cloudinary_cli/cli_group.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python3
2+
import platform
3+
import shutil
4+
5+
import click
6+
import click_log
7+
import cloudinary
8+
9+
from cloudinary_cli.defaults import logger
10+
from cloudinary_cli.utils.config_utils import load_config, refresh_cloudinary_config, \
11+
is_valid_cloudinary_config
12+
from cloudinary_cli.version import __version__ as cli_version
13+
14+
CONTEXT_SETTINGS = dict(max_content_width=shutil.get_terminal_size()[0], terminal_width=shutil.get_terminal_size()[0])
15+
16+
17+
@click.group(context_settings=CONTEXT_SETTINGS)
18+
@click.help_option()
19+
@click.version_option(cli_version, prog_name="Cloudinary CLI",
20+
message=f"%(prog)s, version %(version)s\n"
21+
f"Cloudinary SDK, version {cloudinary.VERSION}\n"
22+
f"Python, version {platform.python_version()}")
23+
@click.option("-c", "--config",
24+
help="""Tell the CLI which account to run the command on by specifying an account environment variable."""
25+
)
26+
@click.option("-C", "--config_saved",
27+
help="""Tell the CLI which account to run the command on by specifying a saved configuration - see
28+
`config` command.""")
29+
@click_log.simple_verbosity_option(logger)
30+
def cli(config, config_saved):
31+
if config:
32+
refresh_cloudinary_config(config)
33+
elif config_saved:
34+
config = load_config()
35+
if config_saved not in config:
36+
raise Exception(f"Config {config_saved} does not exist")
37+
38+
refresh_cloudinary_config(config[config_saved])
39+
40+
if not is_valid_cloudinary_config():
41+
logger.warning("No Cloudinary configuration found.")
42+
43+
return 0

cloudinary_cli/modules/make.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import os
22

3-
from click import command, argument, echo, option
3+
from click import argument, echo, option
44

5+
from cloudinary_cli.cli_group import cli
56
from cloudinary_cli.defaults import TEMPLATE_EXTS, TEMPLATE_FOLDER
67
from cloudinary_cli.utils.utils import load_template, print_help_and_exit
78

89

9-
@command("make", short_help="Return template code for implementing the specified Cloudinary widget.",
10-
help="""\b
10+
@cli.command("make", short_help="Return template code for implementing the specified Cloudinary widget.",
11+
help="""\b
1112
Return template code for implementing the specified Cloudinary widget.
1213
e.g. cld make media library widget
1314
cld make python find all empty folders
@@ -37,7 +38,14 @@ def make(template, list_languages, list_templates):
3738
echo(template_file.name.replace("_", " "))
3839
return True
3940

40-
echo(load_template(language, '_'.join(template)))
41+
template_result = load_template(language, '_'.join(template))
42+
43+
if not template_result:
44+
return False
45+
46+
echo(template_result)
47+
48+
return True
4149

4250

4351
def _handle_language_and_template(language_and_template):

cloudinary_cli/modules/migrate.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import os
12
from os.path import join
23

34
from click import command, argument, option
45
from cloudinary import api
6+
from cloudinary.exceptions import Error
57
from cloudinary.utils import cloudinary_url
68
from requests import head
79

8-
from cloudinary_cli.utils.utils import logger
10+
from cloudinary_cli.utils.utils import logger, log_exception
911

1012

1113
@command("migrate",
@@ -17,14 +19,39 @@
1719
@option("-d", "--delimiter", default="\n", help="The separator used between the URLs. Default: New line")
1820
@option("-v", "--verbose", is_flag=True)
1921
def migrate(upload_mapping, file, delimiter, verbose):
20-
with open(file) as f:
21-
items = f.read().split(delimiter)
22-
mapping = api.upload_mapping(upload_mapping)
23-
items = map(lambda x: cloudinary_url(join(mapping['folder'], x[len(mapping['template']):])),
24-
filter(lambda x: x != '', items))
25-
for i in items:
26-
res = head(i)
22+
if not os.path.exists(file):
23+
logger.error(f"Migration file: '{file}' does not exist")
24+
return False
25+
26+
try:
27+
with open(file) as f:
28+
migration_files = f.read().split(delimiter)
29+
except IOError as e:
30+
log_exception(e, f"Failed reading migration file: '{file}'")
31+
return False
32+
33+
try:
34+
mapping = api.upload_mapping(upload_mapping)
35+
except Error as e:
36+
log_exception(e, f"Failed retrieving upload mapping: '{upload_mapping}'")
37+
return False
38+
39+
exit_status = True
40+
migration_urls = []
41+
42+
for migration_file in filter(None, migration_files): # omit empty lines
43+
if not migration_file.startswith(mapping['template']):
44+
logger.error(f"Skipping '{migration_file}', it does not belong to the upload mapping")
45+
exit_status = False
46+
continue
47+
48+
migration_urls.append(cloudinary_url(join(mapping['folder'], migration_file[len(mapping['template']):])))
49+
50+
for migration_url in migration_urls:
51+
res = head(migration_url)
2752
if res.status_code != 200:
28-
logger.error("Failed uploading asset: " + res.__dict__['headers']['X-Cld-Error'])
53+
logger.error(f"Failed uploading {migration_url}: {res.__dict__['headers']['X-Cld-Error']}")
2954
elif verbose:
30-
logger.info("Uploaded {}".format(i))
55+
logger.info(f"Uploaded {migration_url}")
56+
57+
return exit_status

cloudinary_cli/modules/sync.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from itertools import groupby
44
from os import path, remove
55

6-
from click import command, argument, option, style
6+
from click import command, argument, option, style, UsageError
77
from cloudinary import api
88

99
from cloudinary_cli.utils.api_utils import query_cld_folder, upload_file, download_file
@@ -35,12 +35,12 @@
3535
def sync(local_folder, cloudinary_folder, push, pull, include_hidden, concurrent_workers, force, keep_unique,
3636
deletion_batch_size):
3737
if push == pull:
38-
raise Exception("Please use either the '--push' OR '--pull' options")
38+
raise UsageError("Please use either the '--push' OR '--pull' options")
3939

4040
sync_dir = SyncDir(local_folder, cloudinary_folder, include_hidden, concurrent_workers, force, keep_unique,
4141
deletion_batch_size)
4242

43-
result = 0
43+
result = True
4444
if push:
4545
result = sync_dir.push()
4646
elif pull:

cloudinary_cli/modules/upload_dir.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import glob
2-
from os import getcwd, walk
3-
from os.path import dirname, split, join as path_join, abspath
1+
from os import getcwd
2+
from os.path import dirname, join as path_join
43
from pathlib import Path
54

65
from click import command, argument, option, style
76

87
from cloudinary_cli.utils.api_utils import upload_file
9-
from cloudinary_cli.utils.utils import parse_option_value, logger, run_tasks_concurrently
108
from cloudinary_cli.utils.file_utils import get_destination_folder, is_hidden_path
9+
from cloudinary_cli.utils.utils import parse_option_value, logger, run_tasks_concurrently
1110

1211

1312
@command("upload_dir", help="""Upload a folder of assets, maintaining the folder structure.""")
@@ -32,7 +31,11 @@ def upload_dir(directory, glob_pattern, include_hidden, optional_parameter, opti
3231
items, skipped = {}, {}
3332

3433
dir_to_upload = Path(path_join(getcwd(), directory))
35-
logger.info("Uploading directory '{}'".format(dir_to_upload))
34+
if not dir_to_upload.exists():
35+
logger.error(f"Directory: {dir_to_upload} does not exist")
36+
return False
37+
38+
logger.info(f"Uploading directory '{dir_to_upload}'")
3639
parent = dirname(dir_to_upload)
3740
options = {
3841
**{k: v for k, v in optional_parameter},
@@ -58,5 +61,9 @@ def upload_dir(directory, glob_pattern, include_hidden, optional_parameter, opti
5861
run_tasks_concurrently(upload_file, uploads, concurrent_workers)
5962

6063
logger.info(style("{} resources uploaded".format(len(items)), fg="green"))
64+
6165
if skipped:
6266
logger.warn("{} items skipped".format(len(skipped)))
67+
return False
68+
69+
return True

cloudinary_cli/utils/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def log_exception(e, message=None):
7575

7676
def load_template(language, template_name):
7777
filepath = os.path.join(TEMPLATE_FOLDER, language, template_name)
78+
if not os.path.exists(filepath):
79+
logger.error(f"Template: '{template_name}' for language: '{language}' does not exist")
80+
return False
7881
try:
7982
with open(filepath) as f:
8083
template = Environment(loader=FileSystemLoader(TEMPLATE_FOLDER)).from_string(f.read())

0 commit comments

Comments
 (0)