From bb4dbbe1ab56ab34606e5e40f46dbd58c49d7720 Mon Sep 17 00:00:00 2001 From: Craig Forbes Date: Wed, 30 Dec 2020 21:39:59 -0600 Subject: [PATCH 1/3] Add --path global option to set the mount point --- circup.py | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/circup.py b/circup.py index 910eb95..0eec1bc 100644 --- a/circup.py +++ b/circup.py @@ -363,7 +363,7 @@ def extract_metadata(path): return result -def find_modules(): +def find_modules(device_path): """ Extracts metadata from the connected device and available bundle and returns this as a list of Module instances representing the modules on the @@ -374,7 +374,7 @@ def find_modules(): """ # pylint: disable=broad-except try: - device_modules = get_device_versions() + device_modules = get_device_versions(device_path) bundle_modules = get_bundle_versions() result = [] for name, device_metadata in device_modules.items(): @@ -430,14 +430,13 @@ def get_circuitpython_version(device_path): return circuit_python.split(" ")[-3] -def get_device_versions(): +def get_device_versions(device_path): """ Returns a dictionary of metadata from modules on the connected device. :return: A dictionary of metadata about the modules available on the connected device. """ - device_path = find_device() return get_modules(os.path.join(device_path, "lib")) @@ -584,14 +583,21 @@ def get_bundle(tag): @click.option( "--verbose", is_flag=True, help="Comprehensive logging is sent to stdout." ) +@click.option( + "--path", + type=click.Path(exists=True, file_okay=False), + help="Path to circuit python directory. Overrides automatic path detection." +) @click.version_option( prog_name="CircUp", message="%(prog)s, A CircuitPython module updater. Version %(version)s", ) -def main(verbose): # pragma: no cover +@click.pass_context +def main(ctx, verbose, path): # pragma: no cover """ A tool to manage and update libraries on a CircuitPython device. """ + ctx.ensure_object(dict) if verbose: # Configure additional logging to stdout. global VERBOSE @@ -602,7 +608,11 @@ def main(verbose): # pragma: no cover logger.addHandler(verbose_handler) click.echo("Logging to {}\n".format(LOGFILE)) logger.info("### Started Circup ###") - device_path = find_device() + if path: + device_path = path + else: + device_path = find_device() + ctx.obj["DEVICE_PATH"] = device_path if device_path is None: click.secho("Could not find a connected Adafruit device.", fg="red") sys.exit(1) @@ -630,13 +640,14 @@ def main(verbose): # pragma: no cover @main.command() @click.option("-r", "--requirement", is_flag=True) -def freeze(requirement): # pragma: no cover +@click.pass_context +def freeze(ctx, requirement): # pragma: no cover """ Output details of all the modules found on the connected CIRCUITPYTHON device. Option -r saves output to requirements.txt file """ logger.info("Freeze") - modules = find_modules() + modules = find_modules(ctx.obj["DEVICE_PATH"]) if modules: output = [] for module in modules: @@ -656,7 +667,8 @@ def freeze(requirement): # pragma: no cover @main.command() -def list(): # pragma: no cover +@click.pass_context +def list(ctx): # pragma: no cover """ Lists all out of date modules found on the connected CIRCUITPYTHON device. """ @@ -664,7 +676,7 @@ def list(): # pragma: no cover # Grab out of date modules. data = [("Module", "Version", "Latest", "Major Update")] - modules = [m.row for m in find_modules() if m.outofdate] + modules = [m.row for m in find_modules(ctx.obj["DEVICE_PATH"]) if m.outofdate] if modules: data += modules # Nice tabular display. @@ -698,14 +710,15 @@ def list(): # pragma: no cover @click.option( "--all", is_flag=True, help="Update all modules without Major Version warnings." ) -def update(all): # pragma: no cover +@click.pass_context +def update(ctx, all): # pragma: no cover """ Checks for out-of-date modules on the connected CIRCUITPYTHON device, and prompts the user to confirm updating such modules. """ logger.info("Update") # Grab out of date modules. - modules = [m for m in find_modules() if m.outofdate] + modules = [m for m in find_modules(ctx.obj["DEVICE_PATH"]) if m.outofdate] if modules: click.echo("Found {} module[s] needing update.".format(len(modules))) if not all: @@ -750,7 +763,7 @@ def show(): # pragma: no cover # pylint: disable=too-many-locals,too-many-branches -def install_module(name, py, mod_names): # pragma: no cover +def install_module(device_path, name, py, mod_names): # pragma: no cover """ Finds a connected device and installs a given module name if it is available in the current module bundle and is not already @@ -767,16 +780,13 @@ def install_module(name, py, mod_names): # pragma: no cover if not name: click.echo("No module name provided.") elif name in mod_names: - device_path = find_device() - if device_path is None: - raise IOError("Could not find a connected Adafruit device.") library_path = os.path.join(device_path, "lib") if not os.path.exists(library_path): # pragma: no cover os.makedirs(library_path) metadata = mod_names[name] # Grab device modules to check if module already installed device_modules = [] - for module in find_modules(): + for module in find_modules(device_path): device_modules.append(module.name) if name in device_modules: click.echo("'{}' is already installed.".format(name)) @@ -830,7 +840,8 @@ def install_module(name, py, mod_names): # pragma: no cover @click.argument("name", required=False) @click.option("--py", is_flag=True) @click.option("-r", "--requirement") -def install(name, py, requirement): # pragma: no cover +@click.pass_context +def install(ctx, name, py, requirement): # pragma: no cover """ Install a named module onto the device. This is a very naive / simple hacky proof of concept. Option -r allows specifying a text file to @@ -855,30 +866,28 @@ def install(name, py, requirement): # pragma: no cover line = line.strip() # Remove whitespace (including \n). if line: # Ignore blank lines. module = line.split("==")[0] if "==" in line else line - install_module(module, py, mod_names) + install_module(ctx.obj["DEVICE_PATH"], module, py, mod_names) else: - install_module(name, py, mod_names) + install_module(ctx.obj["DEVICE_PATH"], name, py, mod_names) @main.command() @click.argument("module", nargs=-1) -def uninstall(module): # pragma: no cover +@click.pass_context +def uninstall(ctx, module): # pragma: no cover """ Uninstall a named module(s) from the connected device. Multiple modules can be uninstalled at once by providing more than one module name, each separated by a space. """ for name in module: - device_modules = get_device_versions() + device_modules = get_device_versions(ctx.obj["DEVICE_PATH"]) name = name.lower() mod_names = {} for module_item, metadata in device_modules.items(): mod_names[module_item.replace(".py", "").lower()] = metadata if name in mod_names: - device_path = find_device() - if device_path is None: - raise IOError("Could not find a connected Adafruit device.") - library_path = os.path.join(device_path, "lib") + library_path = os.path.join(ctx.obj["DEVICE_PATH"], "lib") metadata = mod_names[name] module_path = metadata["path"] if os.path.isdir(module_path): From 0c07ea45acddcb5991c1d59505a8a61565aade65 Mon Sep 17 00:00:00 2001 From: Craig Forbes Date: Mon, 4 Jan 2021 16:38:18 -0600 Subject: [PATCH 2/3] Update tests for circup.py changes to support --path option. Update pluggy required version to fix error on python 3.8 --- requirements.txt | 2 +- tests/test_circup.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6b88e16..016acb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ MarkupSafe==1.1.1 more-itertools==7.2.0 packaging==19.1 pkginfo==1.5.0.1 -pluggy==0.12.0 +pluggy==0.13.1 py==1.8.0 pycodestyle==2.5.0 pyflakes==2.1.1 diff --git a/tests/test_circup.py b/tests/test_circup.py index e940636..d1deff7 100644 --- a/tests/test_circup.py +++ b/tests/test_circup.py @@ -352,7 +352,7 @@ def test_find_modules(): ), mock.patch( "circup.os.path.isfile", return_value=True ): - result = circup.find_modules() + result = circup.find_modules("") assert len(result) == 1 assert result[0].name == "adafruit_74hc595" @@ -367,7 +367,7 @@ def test_find_modules_goes_bang(): ), mock.patch("circup.click") as mock_click, mock.patch( "circup.sys.exit" ) as mock_exit: - circup.find_modules() + circup.find_modules("") assert mock_click.echo.call_count == 1 mock_exit.assert_called_once_with(1) @@ -405,11 +405,11 @@ def test_get_device_versions(): """ Ensure get_modules is called with the path for the attached device. """ - with mock.patch("circup.find_device", return_value="CIRCUITPYTHON"), mock.patch( + with mock.patch( "circup.get_modules", return_value="ok" ) as mock_gm: - assert circup.get_device_versions() == "ok" - mock_gm.assert_called_once_with(os.path.join("CIRCUITPYTHON", "lib")) + assert circup.get_device_versions("TESTDIR") == "ok" + mock_gm.assert_called_once_with(os.path.join("TESTDIR", "lib")) def test_get_modules_empty_path(): From 42d1f58ffc787e9b0011275778a76ab548df84d8 Mon Sep 17 00:00:00 2001 From: Craig Forbes Date: Mon, 4 Jan 2021 17:49:48 -0600 Subject: [PATCH 3/3] Reformat with black. --- circup.py | 7 ++----- tests/test_circup.py | 17 ++++------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/circup.py b/circup.py index 0eec1bc..1cd035d 100644 --- a/circup.py +++ b/circup.py @@ -354,10 +354,7 @@ def extract_metadata(path): end = loc # Up to the start of the __version__. version = content[start:end] # Slice the version number. # Create a string version as metadata in the result. - result = { - "__version__": version.decode("utf-8"), - "mpy": True, - } + result = {"__version__": version.decode("utf-8"), "mpy": True} break # Nothing more to do. offset += 1 # ...and again but backtrack by one. return result @@ -586,7 +583,7 @@ def get_bundle(tag): @click.option( "--path", type=click.Path(exists=True, file_okay=False), - help="Path to circuit python directory. Overrides automatic path detection." + help="Path to circuit python directory. Overrides automatic path detection.", ) @click.version_option( prog_name="CircUp", diff --git a/tests/test_circup.py b/tests/test_circup.py index d1deff7..f625ab1 100644 --- a/tests/test_circup.py +++ b/tests/test_circup.py @@ -405,9 +405,7 @@ def test_get_device_versions(): """ Ensure get_modules is called with the path for the attached device. """ - with mock.patch( - "circup.get_modules", return_value="ok" - ) as mock_gm: + with mock.patch("circup.get_modules", return_value="ok") as mock_gm: assert circup.get_device_versions("TESTDIR") == "ok" mock_gm.assert_called_once_with(os.path.join("TESTDIR", "lib")) @@ -452,10 +450,7 @@ def test_get_modules_that_are_directories(): os.path.join("tests", "dir_module", ""), os.path.join("tests", ".hidden_dir", ""), ] - mod_files = [ - "tests/dir_module/my_module.py", - "tests/dir_module/__init__.py", - ] + mod_files = ["tests/dir_module/my_module.py", "tests/dir_module/__init__.py"] with mock.patch("circup.glob.glob", side_effect=[[], [], mods, mod_files, []]): result = circup.get_modules(path) assert len(result) == 1 @@ -473,10 +468,7 @@ def test_get_modules_that_are_directories_with_no_metadata(): """ path = "tests" # mocked away in function. mods = [os.path.join("tests", "bad_module", "")] - mod_files = [ - "tests/bad_module/my_module.py", - "tests/bad_module/__init__.py", - ] + mod_files = ["tests/bad_module/my_module.py", "tests/bad_module/__init__.py"] with mock.patch("circup.glob.glob", side_effect=[[], [], mods, mod_files, []]): result = circup.get_modules(path) assert len(result) == 1 @@ -512,8 +504,7 @@ def test_ensure_latest_bundle_bad_bundle_data(): ), mock.patch("circup.open"), mock.patch( "circup.get_bundle" ) as mock_gb, mock.patch( - "circup.json.load", - side_effect=json.decoder.JSONDecodeError("BANG!", "doc", 1), + "circup.json.load", side_effect=json.decoder.JSONDecodeError("BANG!", "doc", 1) ), mock.patch( "circup.json.dump" ), mock.patch(