Repository: adafruit/circup

Issue:
Error while running CircUp Show during bundle release
I kind of got lucky finding this one. At the time I ran it, the release had been created, but the bundle asset hadn't been finished being generated.

```
bash-3.2$ circup show
Found device at /Volumes/CIRCUITPY, running CircuitPython 5.0.0-alpha.4.
Downloading latest version information.

Traceback (most recent call last):
  File "/usr/local/bin/circup", line 10, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/circup.py", line 719, in show
    available_modules = get_bundle_versions()
  File "/usr/local/lib/python3.7/site-packages/circup.py", line 403, in get_bundle_versions
    ensure_latest_bundle()
  File "/usr/local/lib/python3.7/site-packages/circup.py", line 505, in ensure_latest_bundle
    get_bundle(tag)
  File "/usr/local/lib/python3.7/site-packages/circup.py", line 547, in get_bundle
    r.raise_for_status()
  File "/usr/local/lib/python3.7/site-packages/requests/models.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20191029/adafruit-circuitpython-bundle-py-20191029.zip
```

Comments:

Patch:
diff --git a/circup.py b/circup.py
index da2acab..5e9aef8 100644
--- a/circup.py
+++ b/circup.py
@@ -502,9 +502,21 @@ def ensure_latest_bundle():
                 logger.exception(ex)
     if tag > old_tag:
         logger.info("New version available ({}).".format(tag))
-        get_bundle(tag)
-        with open(BUNDLE_DATA, "w", encoding="utf-8") as data:
-            json.dump({"tag": tag}, data)
+        try:
+            get_bundle(tag)
+            with open(BUNDLE_DATA, "w", encoding="utf-8") as data:
+                json.dump({"tag": tag}, data)
+        except requests.exceptions.HTTPError as ex:
+            # See #20 for reason this this
+            click.secho(
+                (
+                    "There was a problem downloading the bundle. "
+                    "Please try again in a moment."
+                ),
+                fg="red",
+            )
+            logger.exception(ex)
+            sys.exit(1)
     else:
         logger.info("Current library bundle up to date ({}).".format(tag))
 
@@ -761,10 +773,14 @@ def install(name, py):  # pragma: no cover
                 shutil.copyfile(source_path, target_path)
         else:
             # Use pre-compiled mpy modules.
-            module_name = os.path.basename(metadata["path"]).replace(".py", ".mpy")
+            module_name = os.path.basename(metadata["path"]).replace(
+                ".py", ".mpy"
+            )
             if not module_name:
                 # Must be a directory based module.
-                module_name = os.path.basename(os.path.dirname(metadata["path"]))
+                module_name = os.path.basename(
+                    os.path.dirname(metadata["path"])
+                )
             major_version = CPY_VERSION.split(".")[0]
             bundle_platform = "{}mpy".format(major_version)
             bundle_path = ""


Test patch:
diff --git a/tests/test_circup.py b/tests/test_circup.py
index 32e8ae8..40b87ee 100644
--- a/tests/test_circup.py
+++ b/tests/test_circup.py
@@ -26,6 +26,7 @@ import circup
 import ctypes
 import pytest
 import json
+import requests
 from unittest import mock
 
 
@@ -545,8 +546,8 @@ def test_ensure_latest_bundle_bad_bundle_data():
 
 def test_ensure_latest_bundle_to_update():
     """
-    If the version found in the BUNDLE_DATA is out of date, the cause an update
-    to the bundle.
+    If the version found in the BUNDLE_DATA is out of date, then cause an
+    update to the bundle.
     """
     with mock.patch("circup.get_latest_tag", return_value="54321"), mock.patch(
         "circup.os.path.isfile", return_value=True
@@ -561,6 +562,30 @@ def test_ensure_latest_bundle_to_update():
         assert mock_json.dump.call_count == 1  # Current version saved to file.
 
 
+def test_ensure_latest_bundle_to_update_http_error():
+    """
+    If an HTTP error happens during a bundle update, print a friendly
+    error message and exit 1.
+    """
+    with mock.patch("circup.get_latest_tag", return_value="54321"), mock.patch(
+        "circup.os.path.isfile", return_value=True
+    ), mock.patch("circup.open"), mock.patch(
+        "circup.get_bundle", side_effect=requests.exceptions.HTTPError("404")
+    ) as mock_gb, mock.patch(
+        "circup.json"
+    ) as mock_json, mock.patch(
+        "circup.click.secho"
+    ) as mock_click, mock.patch(
+        "circup.sys.exit"
+    ) as mock_exit:
+        mock_json.load.return_value = {"tag": "12345"}
+        circup.ensure_latest_bundle()
+        mock_gb.assert_called_once_with("54321")
+        assert mock_json.dump.call_count == 0  # not saved.
+        mock_click.call_count == 1  # friendly message.
+        mock_exit.assert_called_once_with(1)  # exit 1.
+
+
 def test_ensure_latest_bundle_no_update():
     """
     If the version found in the BUNDLE_DATA is NOT out of date, just log the