diff --git a/circup/__init__.py b/circup/__init__.py index 77e1b1f..33cc551 100644 --- a/circup/__init__.py +++ b/circup/__init__.py @@ -25,6 +25,8 @@ import click import findimports import pkg_resources import requests +import toml +from semver import VersionInfo import update_checker from requests.auth import HTTPBasicAuth from semver import VersionInfo @@ -64,7 +66,7 @@ NOT_MCU_LIBRARIES = [ #: The version of CircuitPython found on the connected device. CPY_VERSION = "" #: Module formats list (and the other form used in github files) -PLATFORMS = {"py": "py", "8mpy": "8.x-mpy"} +PLATFORMS = {"py": "py", "8mpy": "8.x-mpy", "9mpy": "9.x-mpy"} #: Commands that do not require an attached board BOARDLESS_COMMANDS = ["show", "bundle-add", "bundle-remove", "bundle-show"] #: Version identifier for a bad MPY file format @@ -134,7 +136,7 @@ class Bundle: "lib", ) - def requirements_for(self, library_name): + def requirements_for(self, library_name, toml_file=False): """ The requirements file for this library. @@ -143,15 +145,15 @@ class Bundle: """ platform = "py" tag = self.current_tag - requirements_txt = os.path.join( + found_file = os.path.join( self.dir.format(platform=platform), self.basename.format(platform=PLATFORMS[platform], tag=tag), "requirements", library_name, - "requirements.txt", + "requirements.txt" if not toml_file else "pyproject.toml", ) - if os.path.isfile(requirements_txt): - with open(requirements_txt, "r", encoding="utf-8") as read_this: + if os.path.isfile(found_file): + with open(found_file, "r", encoding="utf-8") as read_this: return read_this.read() return None @@ -570,13 +572,12 @@ def ensure_latest_bundle(bundle): # See #20 for reason for this click.secho( ( - "There was a problem downloading the bundle. " - "Please try again in a moment." + "There was a problem downloading that platform bundle. " + "Skipping and using existing download if available." ), fg="red", ) logger.exception(ex) - sys.exit(1) else: logger.info("Current bundle up to date %s.", tag) @@ -775,8 +776,10 @@ def get_bundle(bundle, tag): :param Bundle bundle: the target Bundle object. :param str tag: The GIT tag to use to download the bundle. """ - click.echo("Downloading latest version for {}.\n".format(bundle.key)) + click.echo(f"Downloading latest bundles for {bundle.key} ({tag}).") for platform, github_string in PLATFORMS.items(): + # Report the platform: "8.x-mpy", etc. + click.echo(f"{github_string}:") url = bundle.url_format.format(platform=github_string, tag=tag) logger.info("Downloading bundle: %s", url) r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT) @@ -787,9 +790,9 @@ def get_bundle(bundle, tag): # pylint: enable=no-member total_size = int(r.headers.get("Content-Length")) temp_zip = bundle.zip.format(platform=platform) - with click.progressbar(r.iter_content(1024), length=total_size) as pbar, open( - temp_zip, "wb" - ) as zip_fp: + with click.progressbar( + r.iter_content(1024), label="Extracting:", length=total_size + ) as pbar, open(temp_zip, "wb") as zip_fp: for chunk in pbar: zip_fp.write(chunk) pbar.update(len(chunk)) @@ -1023,6 +1026,11 @@ def get_dependencies(*requested_libraries, mod_names, to_install=()): _requested_libraries.extend( libraries_from_requirements(requirements_txt) ) + + circup_dependencies = get_circup_dependencies(bundle, library) + for circup_dependency in circup_dependencies: + _requested_libraries.append(circup_dependency) + # we've processed this library, remove it from the list _requested_libraries.remove(library) @@ -1031,6 +1039,36 @@ def get_dependencies(*requested_libraries, mod_names, to_install=()): ) +def get_circup_dependencies(bundle, library): + """ + Get the list of circup dependencies from pyproject.toml + e.g. + [circup] + circup_dependencies = ["dependency_name_here"] + + :param bundle: The Bundle to look within + :param library: The Library to find pyproject.toml for and get dependencies from + + :return: The list of dependency libraries that were found + """ + try: + pyproj_toml = bundle.requirements_for(library, toml_file=True) + if pyproj_toml: + pyproj_toml_data = toml.loads(pyproj_toml) + dependencies = pyproj_toml_data["circup"]["circup_dependencies"] + if isinstance(dependencies, list): + return dependencies + + if isinstance(dependencies, str): + return (dependencies,) + + return tuple() + + except KeyError: + # no circup_dependencies in pyproject.toml + return tuple() + + def get_device_versions(device_url): """ Returns a dictionary of metadata from modules on the connected device. diff --git a/requirements.txt b/requirements.txt index 66bd56d..e66f3a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ pytz==2019.2 readme-renderer==24.0 requests==2.31.0 requests-toolbelt==0.9.1 -semver==2.13.0 +semver==3.0.1 six==1.12.0 snowballstemmer==1.9.0 Sphinx==2.2.0 diff --git a/setup.py b/setup.py index 10fcc4b..3a9b862 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ with open(path.join(here, "README.rst"), encoding="utf-8") as f: long_description = f.read() install_requires = [ - "semver~=2.13", + "semver~=3.0", "Click>=8.0", "appdirs>=1.4.3", "requests>=2.22.0", diff --git a/tests/test_circup.py b/tests/test_circup.py index 53e94ec..e1ad769 100644 --- a/tests/test_circup.py +++ b/tests/test_circup.py @@ -858,7 +858,7 @@ def test_ensure_latest_bundle_to_update(): 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. + error message, and use existing bundle. """ tags_data = {TEST_BUNDLE_NAME: "12345"} with mock.patch("circup.Bundle.latest_tag", "54321"), mock.patch( @@ -872,9 +872,7 @@ def test_ensure_latest_bundle_to_update_http_error(): "circup.json" ) as mock_json, mock.patch( "circup.click.secho" - ) as mock_click, mock.patch( - "circup.sys.exit" - ) as mock_exit: + ) as mock_click: circup.Bundle.tags_data = dict() mock_json.load.return_value = tags_data bundle = circup.Bundle(TEST_BUNDLE_NAME) @@ -882,7 +880,6 @@ def test_ensure_latest_bundle_to_update_http_error(): mock_gb.assert_called_once_with(bundle, "54321") assert mock_json.dump.call_count == 0 # not saved. assert mock_click.call_count == 1 # friendly message. - mock_exit.assert_called_once_with(1) # exit 1. def test_ensure_latest_bundle_no_update():