Merge branch 'main' into web_workflow

# Conflicts:
#	circup/__init__.py
This commit is contained in:
foamyguy 2023-10-28 11:34:03 -05:00
commit ae5cc2a9b4
5 changed files with 91 additions and 31 deletions

View file

@ -15,11 +15,12 @@ repos:
- id: pylint
name: lint (code)
types: [python]
exclude: "^(docs/|examples/|setup.py$)"
exclude: "^(docs/|examples/|setup.py$|tests/bad_python.py$)"
- repo: https://github.com/python/black
rev: 22.3.0
hooks:
- id: black
- id: black
exclude: "^tests/bad_python.py$"
- repo: https://github.com/fsfe/reuse-tool
rev: v0.14.0
hooks:

View file

@ -61,7 +61,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", "7mpy": "7.x-mpy", "8mpy": "7.x-mpy"}
PLATFORMS = {"py": "py", "8mpy": "8.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
@ -507,6 +507,7 @@ def clean_library_name(assumed_library_name):
not_standard_names = {
# Assumed Name : Actual Name
"adafruit_adafruitio": "adafruit_io",
"adafruit_asyncio": "asyncio",
"adafruit_busdevice": "adafruit_bus_device",
"adafruit_display_button": "adafruit_button",
"adafruit_neopixel": "neopixel",
@ -1193,8 +1194,8 @@ def _get_modules_file(path):
result[os.path.basename(sfm).replace(".py", "").replace(".mpy", "")] = metadata
for package_path in package_dir_mods:
name = os.path.basename(os.path.dirname(package_path))
py_files = glob.glob(os.path.join(package_path, "*.py"))
mpy_files = glob.glob(os.path.join(package_path, "*.mpy"))
py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
all_files = py_files + mpy_files
# default value
result[name] = {"path": package_path, "mpy": bool(mpy_files)}
@ -1329,7 +1330,15 @@ def libraries_from_imports(code_py, mod_names):
:param str code_py: Full path of the code.py file
:return: sequence of library names
"""
imports = [info.name.split(".", 1)[0] for info in findimports.find_imports(code_py)]
# pylint: disable=broad-except
try:
found_imports = findimports.find_imports(code_py)
except Exception as ex: # broad exception because anything could go wrong
logger.exception(ex)
click.secho('Unable to read the auto file: "{}"'.format(str(ex)), fg="red")
sys.exit(2)
# pylint: enable=broad-except
imports = [info.name.split(".", 1)[0] for info in found_imports]
return [r for r in imports if r in mod_names]
@ -1428,12 +1437,24 @@ def tags_data_save_tag(key, tag):
@click.option(
"--password", help="Password to use for authentication when --host is used."
)
@click.option(
"--board-id",
default=None,
help="Manual Board ID of the CircuitPython device. If provided in combination "
"with --cpy-version, it overrides the detected board ID.",
)
@click.option(
"--cpy-version",
default=None,
help="Manual CircuitPython version. If provided in combination "
"with --board-id, it overrides the detected CPy version.",
)
@click.version_option(
prog_name="CircUp",
message="%(prog)s, A CircuitPython module updater. Version %(version)s",
)
@click.pass_context
def main(ctx, verbose, path, host, password): # pragma: no cover
def main(ctx, verbose, path, host, password, board_id, cpy_version): # pragma: no cover
"""
A tool to manage and update libraries on a CircuitPython device.
"""
@ -1469,7 +1490,11 @@ def main(ctx, verbose, path, host, password): # pragma: no cover
click.secho("Could not find a connected CircuitPython device.", fg="red")
sys.exit(1)
else:
CPY_VERSION, board_id = get_circuitpython_version(device_path)
CPY_VERSION, board_id = (
get_circuitpython_version(device_path)
if board_id is None or cpy_version is None
else (cpy_version, board_id)
)
click.echo(
"Found device at {}, running CircuitPython {}.".format(
device_path, CPY_VERSION
@ -1599,23 +1624,34 @@ def list_cli(ctx): # pragma: no cover
@click.argument(
"modules", required=False, nargs=-1, shell_complete=completion_for_install
)
@click.option("pyext", "--py", is_flag=True)
@click.option("-r", "--requirement", type=click.Path(exists=True, dir_okay=False))
@click.option("--auto/--no-auto", "-a/-A")
@click.option("--auto-file", default="code.py")
@click.option(
"pyext",
"--py",
is_flag=True,
help="Install the .py version of the module(s) instead of the mpy version.",
)
@click.option(
"-r",
"--requirement",
type=click.Path(exists=True, dir_okay=False),
help="specify a text file to install all modules listed in the text file."
" Typically requirements.txt.",
)
@click.option(
"--auto", "-a", is_flag=True, help="Install the modules imported in code.py."
)
@click.option(
"--auto-file",
default=None,
help="Specify the name of a file on the board to read for auto install."
" Also accepts an absolute path or a local ./ path.",
)
@click.pass_context
def install(ctx, modules, pyext, requirement, auto, auto_file): # pragma: no cover
"""
Install a named module(s) onto the device. Multiple modules
can be installed at once by providing more than one module name, each
separated by a space.
Option --py installs .py version of module(s).
Option -r allows specifying a text file to install all modules listed in
the text file.
Option -a installs based on the modules imported by code.py
"""
# TODO: Ensure there's enough space on the device
available_modules = get_bundle_versions(get_bundles_list())
@ -1626,8 +1662,16 @@ def install(ctx, modules, pyext, requirement, auto, auto_file): # pragma: no co
with open(requirement, "r", encoding="utf-8") as rfile:
requirements_txt = rfile.read()
requested_installs = libraries_from_requirements(requirements_txt)
elif auto:
auto_file = os.path.join(ctx.obj["DEVICE_PATH"], auto_file)
elif auto or auto_file:
if auto_file is None:
auto_file = "code.py"
# pass a local file with "./" or "../"
is_relative = auto_file.split(os.sep)[0] in [os.path.curdir, os.path.pardir]
if not os.path.isabs(auto_file) and not is_relative:
auto_file = os.path.join(ctx.obj["DEVICE_PATH"], auto_file or "code.py")
if not os.path.isfile(auto_file):
click.secho(f"Auto file not found: {auto_file}", fg="red")
sys.exit(1)
requested_installs = libraries_from_imports(auto_file, mod_names)
else:
requested_installs = modules

View file

@ -6,7 +6,7 @@ attrs==19.1.0
Babel==2.9.1
black==19.3b0
bleach==3.3.0
certifi==2022.12.7
certifi==2023.7.22
chardet==3.0.4
charset-normalizer==2.0.4
click==8.0.1
@ -26,7 +26,7 @@ packaging==19.1
pkginfo==1.5.0.1
pluggy==0.13.1
py==1.10.0
Pygments==2.7.4
Pygments==2.15.0
pylint==2.9.6
pyparsing==2.4.2
pytest==5.1.2
@ -35,7 +35,7 @@ pytest-faulthandler==2.0.1
pytest-random-order==1.0.4
pytz==2019.2
readme-renderer==24.0
requests==2.26.0
requests==2.31.0
requests-toolbelt==0.9.1
semver==2.13.0
six==1.12.0
@ -51,7 +51,7 @@ toml==0.10.0
tqdm==4.35.0
twine==1.13.0
update-checker==0.18.0
urllib3==1.26.5
urllib3==1.26.18
wcwidth==0.1.7
webencodings==0.5.1
wrapt==1.12.1

6
tests/bad_python.py Normal file
View file

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: disable=all
if True:

View file

@ -96,10 +96,10 @@ def test_Bundle_lib_dir():
"adafruit/adafruit-circuitpython-bundle-py/"
"adafruit-circuitpython-bundle-py-TESTTAG/lib"
)
assert bundle.lib_dir("7mpy") == (
assert bundle.lib_dir("8mpy") == (
"DATA_DIR/"
"adafruit/adafruit-circuitpython-bundle-7mpy/"
"adafruit-circuitpython-bundle-7.x-mpy-TESTTAG/lib"
"adafruit/adafruit-circuitpython-bundle-8mpy/"
"adafruit-circuitpython-bundle-8.x-mpy-TESTTAG/lib"
)
@ -312,7 +312,7 @@ def test_Module_mpy_mismatch():
"""
path = os.path.join("foo", "bar", "baz", "module.mpy")
repo = "https://github.com/adafruit/SomeLibrary.git"
with mock.patch("circup.CPY_VERSION", "7.0.0"):
with mock.patch("circup.CPY_VERSION", "8.0.0"):
bundle = circup.Bundle(TEST_BUNDLE_NAME)
m1 = circup.Module(path, repo, "1.2.3", "1.2.3", True, bundle, (None, None))
m2 = circup.Module(
@ -328,7 +328,7 @@ def test_Module_mpy_mismatch():
assert m2.outofdate is True
assert m3.mpy_mismatch is False
assert m3.outofdate is False
with mock.patch("circup.CPY_VERSION", "7.0.0"):
with mock.patch("circup.CPY_VERSION", "8.0.0"):
assert m1.mpy_mismatch is False
assert m1.outofdate is False
assert m2.mpy_mismatch is False
@ -366,7 +366,7 @@ def test_Module_row():
path = os.path.join("foo", "bar", "baz", "module.py")
repo = "https://github.com/adafruit/SomeLibrary.git"
with mock.patch("circup.os.path.isfile", return_value=True), mock.patch(
"circup.CPY_VERSION", "7.0.0"
"circup.CPY_VERSION", "8.0.0"
):
m = circup.Module(path, repo, "1.2.3", None, False, bundle, (None, None))
assert m.row == ("module", "1.2.3", "unknown", "Major Version")
@ -1030,3 +1030,12 @@ def test_libraries_from_imports():
"adafruit_esp32spi",
"adafruit_hid",
]
def test_libraries_from_imports_bad():
"""Ensure that we catch an import error"""
TEST_BUNDLE_MODULES = {"one.py": {}, "two.py": {}, "three.py": {}}
runner = CliRunner()
with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
result = runner.invoke(circup.install, ["--auto-file", "./tests/bad_python.py"])
assert result.exit_code == 2