circup 2.1.1__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {circup-2.1.1 → circup-2.2.0}/.github/workflows/build.yml +1 -1
- {circup-2.1.1/circup.egg-info → circup-2.2.0}/PKG-INFO +3 -3
- {circup-2.1.1 → circup-2.2.0}/circup/backends.py +51 -19
- {circup-2.1.1 → circup-2.2.0}/circup/command_utils.py +165 -11
- {circup-2.1.1 → circup-2.2.0}/circup/commands.py +5 -25
- {circup-2.1.1 → circup-2.2.0}/circup/shared.py +1 -1
- {circup-2.1.1 → circup-2.2.0/circup.egg-info}/PKG-INFO +3 -3
- {circup-2.1.1 → circup-2.2.0}/circup.egg-info/SOURCES.txt +8 -1
- {circup-2.1.1 → circup-2.2.0}/circup.egg-info/requires.txt +0 -1
- {circup-2.1.1 → circup-2.2.0}/requirements.txt +0 -1
- {circup-2.1.1 → circup-2.2.0}/tests/import_styles.py +3 -0
- circup-2.2.0/tests/mock_device/boot_out.txt +3 -0
- circup-2.2.0/tests/mock_device/import_styles_sub.py +5 -0
- circup-2.2.0/tests/mock_device_2/.gitignore +4 -0
- circup-2.2.0/tests/mock_device_2/boot_out.txt.license +3 -0
- circup-2.2.0/tests/mock_device_2/code.py +7 -0
- circup-2.2.0/tests/mock_device_2/package/__init__.py +6 -0
- circup-2.2.0/tests/mock_device_2/package/other.py +5 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_circup.py +163 -12
- {circup-2.1.1 → circup-2.2.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-2.1.1 → circup-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-2.1.1 → circup-2.2.0}/.github/workflows/release.yml +0 -0
- {circup-2.1.1 → circup-2.2.0}/.gitignore +0 -0
- {circup-2.1.1 → circup-2.2.0}/.isort.cfg +0 -0
- {circup-2.1.1 → circup-2.2.0}/.pre-commit-config.yaml +0 -0
- {circup-2.1.1 → circup-2.2.0}/.pylintrc +0 -0
- {circup-2.1.1 → circup-2.2.0}/CODE_OF_CONDUCT.rst +0 -0
- {circup-2.1.1 → circup-2.2.0}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/CONTRIBUTING.rst +0 -0
- {circup-2.1.1 → circup-2.2.0}/CONTRIBUTING.rst.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/LICENSE +0 -0
- {circup-2.1.1 → circup-2.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/LICENSES/MIT.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/LICENSES/Unlicense.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/README.rst +0 -0
- {circup-2.1.1 → circup-2.2.0}/README.rst.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/__init__.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/bundle.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/config/bundle_config.json +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/config/bundle_config.json.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/logging.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/module.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/wwshell/README.rst +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/wwshell/README.rst.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/wwshell/__init__.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup/wwshell/commands.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup.egg-info/dependency_links.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup.egg-info/entry_points.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/circup.egg-info/top_level.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/_static/favicon.ico +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/_static/favicon.ico.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/conf.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/index.rst +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/index.rst.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/logo.png +0 -0
- {circup-2.1.1 → circup-2.2.0}/docs/logo.png.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/optional_requirements.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/optional_requirements.txt.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/pyproject.toml +0 -0
- {circup-2.1.1 → circup-2.2.0}/readthedocs.yml +0 -0
- {circup-2.1.1 → circup-2.2.0}/requirements.txt.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/setup.cfg +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/__init__.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/bad_module/__init__.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/bad_module/my_module.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/bad_python.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/bundle.json +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/bundle.json.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/device.json +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/device.json.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/dir_module/__init__.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/dir_module/my_module.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/local_module.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/local_module_cp7.mpy +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/local_module_cp7.mpy.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-2.1.1/tests/mock_device → circup-2.2.0/tests/mock_device_2}/boot_out.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mount_exists.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mount_exists.txt.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mount_missing.txt +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/mount_missing.txt.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/remote_module.py +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_bundle_config.json +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_bundle_config.json.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_bundle_config_local.json +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_bundle_config_local.json.license +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_module.mpy +0 -0
- {circup-2.1.1 → circup-2.2.0}/tests/test_module.mpy.license +0 -0
|
@@ -16,7 +16,7 @@ jobs:
|
|
|
16
16
|
run: echo "$GITHUB_CONTEXT"
|
|
17
17
|
- name: Translate Repo Name For Build Tools filename_prefix
|
|
18
18
|
id: repo-name
|
|
19
|
-
run: echo
|
|
19
|
+
run: echo "repo-name=circup" >> $GITHUB_OUTPUT
|
|
20
20
|
- name: Set up Python 3.11
|
|
21
21
|
uses: actions/setup-python@v1
|
|
22
22
|
with:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,7 +47,6 @@ Description-Content-Type: text/x-rst
|
|
|
47
47
|
License-File: LICENSE
|
|
48
48
|
Requires-Dist: appdirs
|
|
49
49
|
Requires-Dist: Click
|
|
50
|
-
Requires-Dist: findimports
|
|
51
50
|
Requires-Dist: requests
|
|
52
51
|
Requires-Dist: semver
|
|
53
52
|
Requires-Dist: toml
|
|
@@ -57,6 +56,7 @@ Requires-Dist: pytest; extra == "optional"
|
|
|
57
56
|
Requires-Dist: pytest-cov; extra == "optional"
|
|
58
57
|
Requires-Dist: pytest-faulthandler; extra == "optional"
|
|
59
58
|
Requires-Dist: pytest-random-order; extra == "optional"
|
|
59
|
+
Dynamic: license-file
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
Circup
|
|
@@ -233,6 +233,12 @@ class Backend:
|
|
|
233
233
|
"""
|
|
234
234
|
raise NotImplementedError
|
|
235
235
|
|
|
236
|
+
def get_file_content(self, target_file):
|
|
237
|
+
"""
|
|
238
|
+
To be overridden by subclass
|
|
239
|
+
"""
|
|
240
|
+
raise NotImplementedError
|
|
241
|
+
|
|
236
242
|
def get_free_space(self):
|
|
237
243
|
"""
|
|
238
244
|
To be overridden by subclass
|
|
@@ -618,6 +624,20 @@ class WebBackend(Backend):
|
|
|
618
624
|
f"Downloaded File: {os.path.join(location_to_paste, file_name)}"
|
|
619
625
|
)
|
|
620
626
|
|
|
627
|
+
def get_file_content(self, target_file):
|
|
628
|
+
"""
|
|
629
|
+
Get the content of a file from the MCU drive
|
|
630
|
+
:param target_file: The file on the MCU to download
|
|
631
|
+
:return:
|
|
632
|
+
"""
|
|
633
|
+
auth = HTTPBasicAuth("", self.password)
|
|
634
|
+
with self.session.get(
|
|
635
|
+
self.FS_URL + target_file, timeout=self.timeout, auth=auth
|
|
636
|
+
) as r:
|
|
637
|
+
if r.status_code == 404:
|
|
638
|
+
return None
|
|
639
|
+
return r.content # .decode("utf8")
|
|
640
|
+
|
|
621
641
|
def install_module_mpy(self, bundle, metadata):
|
|
622
642
|
"""
|
|
623
643
|
:param bundle library bundle.
|
|
@@ -655,19 +675,6 @@ class WebBackend(Backend):
|
|
|
655
675
|
else:
|
|
656
676
|
self.install_file_http(source_path, location=location)
|
|
657
677
|
|
|
658
|
-
def get_auto_file_path(self, auto_file_path):
|
|
659
|
-
"""
|
|
660
|
-
Make a local temp copy of the --auto file from the device.
|
|
661
|
-
Returns the path to the local copy.
|
|
662
|
-
"""
|
|
663
|
-
url = auto_file_path
|
|
664
|
-
auth = HTTPBasicAuth("", self.password)
|
|
665
|
-
with self.session.get(url, auth=auth, timeout=self.timeout) as r:
|
|
666
|
-
r.raise_for_status()
|
|
667
|
-
with open(LOCAL_CODE_PY_COPY, "w", encoding="utf-8") as f:
|
|
668
|
-
f.write(r.text)
|
|
669
|
-
return LOCAL_CODE_PY_COPY
|
|
670
|
-
|
|
671
678
|
def uninstall(self, device_path, module_path):
|
|
672
679
|
"""
|
|
673
680
|
Uninstall given module on device using REST API.
|
|
@@ -958,12 +965,6 @@ class DiskBackend(Backend):
|
|
|
958
965
|
# Copy file.
|
|
959
966
|
shutil.copyfile(source_path, target_path)
|
|
960
967
|
|
|
961
|
-
def get_auto_file_path(self, auto_file_path):
|
|
962
|
-
"""
|
|
963
|
-
Returns the path on the device to the file to be read for --auto.
|
|
964
|
-
"""
|
|
965
|
-
return auto_file_path
|
|
966
|
-
|
|
967
968
|
def uninstall(self, device_path, module_path):
|
|
968
969
|
"""
|
|
969
970
|
Uninstall module using local file system.
|
|
@@ -1014,6 +1015,18 @@ class DiskBackend(Backend):
|
|
|
1014
1015
|
"""
|
|
1015
1016
|
return os.path.join(self.device_location, filename)
|
|
1016
1017
|
|
|
1018
|
+
def get_file_content(self, target_file):
|
|
1019
|
+
"""
|
|
1020
|
+
Get the content of a file from the MCU drive
|
|
1021
|
+
:param target_file: The file on the MCU to download
|
|
1022
|
+
:return:
|
|
1023
|
+
"""
|
|
1024
|
+
file_path = self.get_file_path(target_file)
|
|
1025
|
+
if os.path.exists(file_path):
|
|
1026
|
+
with open(file_path, "rb") as file:
|
|
1027
|
+
return file.read()
|
|
1028
|
+
return None
|
|
1029
|
+
|
|
1017
1030
|
def is_device_present(self):
|
|
1018
1031
|
"""
|
|
1019
1032
|
returns True if the device is currently connected
|
|
@@ -1027,3 +1040,22 @@ class DiskBackend(Backend):
|
|
|
1027
1040
|
# pylint: disable=unused-variable
|
|
1028
1041
|
_, total, free = shutil.disk_usage(self.device_location)
|
|
1029
1042
|
return free
|
|
1043
|
+
|
|
1044
|
+
def list_dir(self, dirpath):
|
|
1045
|
+
"""
|
|
1046
|
+
Returns the list of files located in the given dirpath.
|
|
1047
|
+
"""
|
|
1048
|
+
files_list = []
|
|
1049
|
+
files = os.listdir(os.path.join(self.device_location, dirpath))
|
|
1050
|
+
for file_name in files:
|
|
1051
|
+
file = os.path.join(self.device_location, dirpath, file_name)
|
|
1052
|
+
stat = os.stat(file)
|
|
1053
|
+
files_list.append(
|
|
1054
|
+
{
|
|
1055
|
+
"name": file_name,
|
|
1056
|
+
"directory": os.path.isdir(file),
|
|
1057
|
+
"modified_ns": stat.st_mtime_ns,
|
|
1058
|
+
"file_size": stat.st_size,
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
return files_list
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Functions called from commands in order to provide behaviors and return information.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import ast
|
|
8
9
|
import ctypes
|
|
9
10
|
import glob
|
|
10
11
|
import os
|
|
@@ -16,7 +17,6 @@ import zipfile
|
|
|
16
17
|
import json
|
|
17
18
|
import re
|
|
18
19
|
import toml
|
|
19
|
-
import findimports
|
|
20
20
|
import requests
|
|
21
21
|
import click
|
|
22
22
|
|
|
@@ -41,6 +41,25 @@ WARNING_IGNORE_MODULES = (
|
|
|
41
41
|
"circuitpython-typing",
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
+
CODE_FILES = [
|
|
45
|
+
"code.txt",
|
|
46
|
+
"code.py",
|
|
47
|
+
"main.py",
|
|
48
|
+
"main.txt",
|
|
49
|
+
"code.txt.py",
|
|
50
|
+
"code.py.txt",
|
|
51
|
+
"code.txt.txt",
|
|
52
|
+
"code.py.py",
|
|
53
|
+
"main.txt.py",
|
|
54
|
+
"main.py.txt",
|
|
55
|
+
"main.txt.txt",
|
|
56
|
+
"main.py.py",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CodeParsingException(Exception):
|
|
61
|
+
"""Exception thrown when parsing code with ast fails"""
|
|
62
|
+
|
|
44
63
|
|
|
45
64
|
def clean_library_name(assumed_library_name):
|
|
46
65
|
"""
|
|
@@ -605,23 +624,158 @@ def tags_data_save_tag(key, tag):
|
|
|
605
624
|
json.dump(tags_data, data)
|
|
606
625
|
|
|
607
626
|
|
|
608
|
-
def
|
|
627
|
+
def imports_from_code(full_content):
|
|
609
628
|
"""
|
|
610
629
|
Parse the given code.py file and return the imported libraries
|
|
630
|
+
Note that it's impossible at that level to differentiate between
|
|
631
|
+
import module.property and import module.submodule, so we try both
|
|
611
632
|
|
|
612
|
-
:param str
|
|
633
|
+
:param str full_content: Code to read imports from
|
|
634
|
+
:param str module_name: Name of the module the code is from
|
|
613
635
|
:return: sequence of library names
|
|
614
636
|
"""
|
|
615
|
-
# pylint: disable=
|
|
637
|
+
# pylint: disable=too-many-branches
|
|
616
638
|
try:
|
|
617
|
-
|
|
618
|
-
except
|
|
619
|
-
|
|
620
|
-
|
|
639
|
+
par = ast.parse(full_content)
|
|
640
|
+
except (SyntaxError, ValueError) as err:
|
|
641
|
+
raise CodeParsingException(err) from err
|
|
642
|
+
|
|
643
|
+
imports = set()
|
|
644
|
+
for thing in ast.walk(par):
|
|
645
|
+
# import module and import module.submodule
|
|
646
|
+
if isinstance(thing, ast.Import):
|
|
647
|
+
for alias in thing.names:
|
|
648
|
+
imports.add(alias.name)
|
|
649
|
+
# from x import y
|
|
650
|
+
if isinstance(thing, ast.ImportFrom):
|
|
651
|
+
if thing.module:
|
|
652
|
+
# from [.][.]module import names
|
|
653
|
+
module = ("." * thing.level) + thing.module
|
|
654
|
+
imports.add(module)
|
|
655
|
+
for alias in thing.names:
|
|
656
|
+
imports.add(".".join([module, alias.name]))
|
|
657
|
+
else:
|
|
658
|
+
# from . import names
|
|
659
|
+
for alias in thing.names:
|
|
660
|
+
imports.add(alias.name)
|
|
661
|
+
|
|
662
|
+
# import parent modules (in practice it's the __init__.py)
|
|
663
|
+
for name in list(imports):
|
|
664
|
+
if "*" in name:
|
|
665
|
+
imports.remove(name)
|
|
666
|
+
continue
|
|
667
|
+
names = name.split(".")
|
|
668
|
+
for i in range(len(names)):
|
|
669
|
+
module = ".".join(names[: i + 1])
|
|
670
|
+
if module:
|
|
671
|
+
imports.add(module)
|
|
672
|
+
|
|
673
|
+
return sorted(imports)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def get_all_imports(
|
|
677
|
+
backend, auto_file_content, mod_names, current_module, visited=None
|
|
678
|
+
):
|
|
679
|
+
"""
|
|
680
|
+
Recursively retrieve imports from files on the backend
|
|
681
|
+
|
|
682
|
+
:param Backend backend: The current backend object
|
|
683
|
+
:param str auto_file_content: Content of the python file to analyse
|
|
684
|
+
:param list mod_names: Lits of supported bundle mod names
|
|
685
|
+
:param str current_module: Name of the call context module if recursive call
|
|
686
|
+
:param set visited: Modules previously visited
|
|
687
|
+
:return: sequence of library names
|
|
688
|
+
"""
|
|
689
|
+
if visited is None:
|
|
690
|
+
visited = set()
|
|
691
|
+
visited.add(current_module)
|
|
692
|
+
|
|
693
|
+
requested_installs = []
|
|
694
|
+
try:
|
|
695
|
+
imports = imports_from_code(auto_file_content)
|
|
696
|
+
except CodeParsingException as err:
|
|
697
|
+
click.secho(f"Error parsing {current_module}:\n {err}", fg="red")
|
|
621
698
|
sys.exit(2)
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
699
|
+
|
|
700
|
+
for install in imports:
|
|
701
|
+
if install in visited:
|
|
702
|
+
continue
|
|
703
|
+
if install in mod_names:
|
|
704
|
+
requested_installs.append(install)
|
|
705
|
+
else:
|
|
706
|
+
# relative module paths
|
|
707
|
+
if install.startswith(".."):
|
|
708
|
+
install_module = ".".join(current_module.split(".")[:-2])
|
|
709
|
+
install_module = install_module + "." + install[2:]
|
|
710
|
+
elif install.startswith("."):
|
|
711
|
+
install_module = ".".join(current_module.split(".")[:-1])
|
|
712
|
+
install_module = install_module + "." + install[1:]
|
|
713
|
+
else:
|
|
714
|
+
install_module = install
|
|
715
|
+
# possible files for the module: .py or __init__.py (if directory)
|
|
716
|
+
file_name = os.path.join(*install_module.split(".")) + ".py"
|
|
717
|
+
exists = backend.file_exists(file_name)
|
|
718
|
+
if not exists:
|
|
719
|
+
file_name = os.path.join(*install_module.split("."), "__init__.py")
|
|
720
|
+
exists = backend.file_exists(file_name)
|
|
721
|
+
if not exists:
|
|
722
|
+
continue
|
|
723
|
+
install_module += ".__init__"
|
|
724
|
+
# get the content and parse it recursively
|
|
725
|
+
auto_file_content = backend.get_file_content(file_name)
|
|
726
|
+
if auto_file_content:
|
|
727
|
+
sub_imports = get_all_imports(
|
|
728
|
+
backend, auto_file_content, mod_names, install_module, visited
|
|
729
|
+
)
|
|
730
|
+
requested_installs.extend(sub_imports)
|
|
731
|
+
|
|
732
|
+
return sorted(requested_installs)
|
|
733
|
+
# [r for r in requested_installs if r in mod_names]
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def libraries_from_auto_file(backend, auto_file, mod_names):
|
|
737
|
+
"""
|
|
738
|
+
Parse the input auto_file path and/or use the workflow to find the most
|
|
739
|
+
appropriate code.py script. Then return the list of imports
|
|
740
|
+
|
|
741
|
+
:param Backend backend: The current backend object
|
|
742
|
+
:param str auto_file: Path of the candidate auto file or None
|
|
743
|
+
:return: sequence of library names
|
|
744
|
+
"""
|
|
745
|
+
# find the current main file based on Circuitpython's rules
|
|
746
|
+
if auto_file is None:
|
|
747
|
+
root_files = [
|
|
748
|
+
file["name"] for file in backend.list_dir("") if not file["directory"]
|
|
749
|
+
]
|
|
750
|
+
for main_file in CODE_FILES:
|
|
751
|
+
if main_file in root_files:
|
|
752
|
+
auto_file = main_file
|
|
753
|
+
break
|
|
754
|
+
# still no code file found
|
|
755
|
+
if auto_file is None:
|
|
756
|
+
click.secho(
|
|
757
|
+
"No default code file found. See valid names:\n"
|
|
758
|
+
"https://docs.circuitpython.org/en/latest/README.html#behavior",
|
|
759
|
+
fg="red",
|
|
760
|
+
)
|
|
761
|
+
sys.exit(1)
|
|
762
|
+
|
|
763
|
+
# pass a local file with "./" or "../"
|
|
764
|
+
is_relative = auto_file.split(os.sep)[0] in [os.path.curdir, os.path.pardir]
|
|
765
|
+
if os.path.isabs(auto_file) or is_relative:
|
|
766
|
+
with open(auto_file, "r", encoding="UTF8") as fp:
|
|
767
|
+
auto_file_content = fp.read()
|
|
768
|
+
else:
|
|
769
|
+
auto_file_content = backend.get_file_content(auto_file)
|
|
770
|
+
|
|
771
|
+
if auto_file_content is None:
|
|
772
|
+
click.secho(f"Auto file not found: {auto_file}", fg="red")
|
|
773
|
+
sys.exit(1)
|
|
774
|
+
|
|
775
|
+
# from file name to module name (in case it's in a subpackage)
|
|
776
|
+
click.secho(f"Finding imports from: {auto_file}", fg="green")
|
|
777
|
+
current_module = auto_file.rstrip(".py").replace(os.path.sep, ".")
|
|
778
|
+
return get_all_imports(backend, auto_file_content, mod_names, current_module)
|
|
625
779
|
|
|
626
780
|
|
|
627
781
|
def get_device_path(host, port, password, path):
|
|
@@ -34,7 +34,7 @@ from circup.command_utils import (
|
|
|
34
34
|
completion_for_install,
|
|
35
35
|
get_bundle_versions,
|
|
36
36
|
libraries_from_requirements,
|
|
37
|
-
|
|
37
|
+
libraries_from_auto_file,
|
|
38
38
|
get_dependencies,
|
|
39
39
|
get_bundles_local_dict,
|
|
40
40
|
save_local_bundles,
|
|
@@ -342,32 +342,12 @@ def install(
|
|
|
342
342
|
requirements_txt = rfile.read()
|
|
343
343
|
requested_installs = libraries_from_requirements(requirements_txt)
|
|
344
344
|
elif auto or auto_file:
|
|
345
|
-
|
|
346
|
-
auto_file
|
|
347
|
-
|
|
348
|
-
# pass a local file with "./" or "../"
|
|
349
|
-
is_relative = not isinstance(ctx.obj["backend"], WebBackend) or auto_file.split(
|
|
350
|
-
os.sep
|
|
351
|
-
)[0] in [os.path.curdir, os.path.pardir]
|
|
352
|
-
if not os.path.isabs(auto_file) and not is_relative:
|
|
353
|
-
auto_file = ctx.obj["backend"].get_file_path(auto_file or "code.py")
|
|
354
|
-
|
|
355
|
-
auto_file_path = ctx.obj["backend"].get_auto_file_path(auto_file)
|
|
356
|
-
print(f"Auto file path: {auto_file_path}")
|
|
357
|
-
if not os.path.isfile(auto_file_path):
|
|
358
|
-
# fell through to here when run from random folder on windows - ask backend.
|
|
359
|
-
new_auto_file = ctx.obj["backend"].get_file_path(auto_file)
|
|
360
|
-
if os.path.isfile(new_auto_file):
|
|
361
|
-
auto_file = new_auto_file
|
|
362
|
-
auto_file_path = ctx.obj["backend"].get_auto_file_path(auto_file)
|
|
363
|
-
print(f"Auto file path: {auto_file_path}")
|
|
364
|
-
else:
|
|
365
|
-
click.secho(f"Auto file not found: {auto_file}", fg="red")
|
|
366
|
-
sys.exit(1)
|
|
367
|
-
|
|
368
|
-
requested_installs = libraries_from_code_py(auto_file_path, mod_names)
|
|
345
|
+
requested_installs = libraries_from_auto_file(
|
|
346
|
+
ctx.obj["backend"], auto_file, mod_names
|
|
347
|
+
)
|
|
369
348
|
else:
|
|
370
349
|
requested_installs = modules
|
|
350
|
+
|
|
371
351
|
requested_installs = sorted(set(requested_installs))
|
|
372
352
|
click.echo(f"Searching for dependencies for: {requested_installs}")
|
|
373
353
|
to_install = get_dependencies(requested_installs, mod_names=mod_names)
|
|
@@ -21,7 +21,7 @@ BAD_FILE_FORMAT = "Invalid"
|
|
|
21
21
|
DATA_DIR = appdirs.user_data_dir(appname="circup", appauthor="adafruit")
|
|
22
22
|
|
|
23
23
|
#: Module formats list (and the other form used in github files)
|
|
24
|
-
PLATFORMS = {"py": "py", "
|
|
24
|
+
PLATFORMS = {"py": "py", "9mpy": "9.x-mpy"}
|
|
25
25
|
|
|
26
26
|
#: Timeout for requests calls like get()
|
|
27
27
|
REQUESTS_TIMEOUT = 30
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,7 +47,6 @@ Description-Content-Type: text/x-rst
|
|
|
47
47
|
License-File: LICENSE
|
|
48
48
|
Requires-Dist: appdirs
|
|
49
49
|
Requires-Dist: Click
|
|
50
|
-
Requires-Dist: findimports
|
|
51
50
|
Requires-Dist: requests
|
|
52
51
|
Requires-Dist: semver
|
|
53
52
|
Requires-Dist: toml
|
|
@@ -57,6 +56,7 @@ Requires-Dist: pytest; extra == "optional"
|
|
|
57
56
|
Requires-Dist: pytest-cov; extra == "optional"
|
|
58
57
|
Requires-Dist: pytest-faulthandler; extra == "optional"
|
|
59
58
|
Requires-Dist: pytest-random-order; extra == "optional"
|
|
59
|
+
Dynamic: license-file
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
Circup
|
|
@@ -77,4 +77,11 @@ tests/dir_module/__init__.py
|
|
|
77
77
|
tests/dir_module/my_module.py
|
|
78
78
|
tests/mock_device/boot_out.txt
|
|
79
79
|
tests/mock_device/boot_out.txt.license
|
|
80
|
-
tests/mock_device/
|
|
80
|
+
tests/mock_device/import_styles_sub.py
|
|
81
|
+
tests/mock_device/lib/adafruit_waveform/.gitkeep
|
|
82
|
+
tests/mock_device_2/.gitignore
|
|
83
|
+
tests/mock_device_2/boot_out.txt
|
|
84
|
+
tests/mock_device_2/boot_out.txt.license
|
|
85
|
+
tests/mock_device_2/code.py
|
|
86
|
+
tests/mock_device_2/package/__init__.py
|
|
87
|
+
tests/mock_device_2/package/other.py
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
# pylint: disable=all
|
|
5
|
+
import os, sys
|
|
5
6
|
import adafruit_bus_device
|
|
6
7
|
from adafruit_button import Button
|
|
7
8
|
from adafruit_esp32spi import adafruit_esp32spi_socketpool
|
|
9
|
+
from adafruit_display_text import wrap_text_to_pixels, wrap_text_to_lines
|
|
8
10
|
import adafruit_hid.consumer_control
|
|
11
|
+
import import_styles_sub
|
|
@@ -42,6 +42,9 @@ from circup.command_utils import (
|
|
|
42
42
|
ensure_latest_bundle,
|
|
43
43
|
get_bundle,
|
|
44
44
|
get_bundles_dict,
|
|
45
|
+
imports_from_code,
|
|
46
|
+
get_all_imports,
|
|
47
|
+
libraries_from_auto_file,
|
|
45
48
|
)
|
|
46
49
|
from circup.shared import PLATFORMS
|
|
47
50
|
from circup.module import Module
|
|
@@ -94,10 +97,10 @@ def test_Bundle_lib_dir():
|
|
|
94
97
|
"adafruit/adafruit-circuitpython-bundle-py/"
|
|
95
98
|
"adafruit-circuitpython-bundle-py-TESTTAG/lib"
|
|
96
99
|
)
|
|
97
|
-
assert bundle.lib_dir("
|
|
100
|
+
assert bundle.lib_dir("9mpy") == (
|
|
98
101
|
circup.shared.DATA_DIR + "/"
|
|
99
|
-
"adafruit/adafruit-circuitpython-bundle-
|
|
100
|
-
"adafruit-circuitpython-bundle-
|
|
102
|
+
"adafruit/adafruit-circuitpython-bundle-9mpy/"
|
|
103
|
+
"adafruit-circuitpython-bundle-9.x-mpy-TESTTAG/lib"
|
|
101
104
|
)
|
|
102
105
|
|
|
103
106
|
|
|
@@ -119,7 +122,7 @@ def test_get_bundles_dict():
|
|
|
119
122
|
"""
|
|
120
123
|
with mock.patch(
|
|
121
124
|
"circup.command_utils.BUNDLE_CONFIG_FILE", TEST_BUNDLE_CONFIG_JSON
|
|
122
|
-
), mock.patch("circup.
|
|
125
|
+
), mock.patch("circup.command_utils.BUNDLE_CONFIG_LOCAL", ""):
|
|
123
126
|
bundles_dict = get_bundles_dict()
|
|
124
127
|
assert bundles_dict == TEST_BUNDLE_DATA
|
|
125
128
|
|
|
@@ -378,6 +381,16 @@ def test_Module_mpy_mismatch():
|
|
|
378
381
|
assert m2.outofdate is False
|
|
379
382
|
assert m3.mpy_mismatch is True
|
|
380
383
|
assert m3.outofdate is True
|
|
384
|
+
with mock.patch(
|
|
385
|
+
"circup.backends.DiskBackend.get_circuitpython_version",
|
|
386
|
+
return_value=("9.0.0", ""),
|
|
387
|
+
):
|
|
388
|
+
assert m1.mpy_mismatch is False
|
|
389
|
+
assert m1.outofdate is False
|
|
390
|
+
assert m2.mpy_mismatch is True
|
|
391
|
+
assert m2.outofdate is True
|
|
392
|
+
assert m3.mpy_mismatch is True
|
|
393
|
+
assert m3.outofdate is True
|
|
381
394
|
|
|
382
395
|
|
|
383
396
|
def test_Module_major_update_bad_versions():
|
|
@@ -419,14 +432,16 @@ def test_Module_row():
|
|
|
419
432
|
repo = "https://github.com/adafruit/SomeLibrary.git"
|
|
420
433
|
with mock.patch("circup.os.path.isfile", return_value=True), mock.patch(
|
|
421
434
|
"circup.backends.DiskBackend.get_circuitpython_version",
|
|
422
|
-
return_value=("
|
|
435
|
+
return_value=("9.0.0", ""),
|
|
423
436
|
), mock.patch("circup.logger.warning") as mock_logger:
|
|
424
437
|
backend = DiskBackend("mock_device", mock_logger)
|
|
425
438
|
m = Module(name, backend, repo, "1.2.3", None, False, bundle, (None, None))
|
|
426
439
|
assert m.row == ("module", "1.2.3", "unknown", "Major Version")
|
|
427
440
|
m = Module(name, backend, repo, "1.2.3", "1.3.4", False, bundle, (None, None))
|
|
428
441
|
assert m.row == ("module", "1.2.3", "1.3.4", "Minor Version")
|
|
429
|
-
m = Module(
|
|
442
|
+
m = Module(
|
|
443
|
+
name, backend, repo, "1.2.3", "1.2.3", True, bundle, ("8.0.0", "9.0.0")
|
|
444
|
+
)
|
|
430
445
|
assert m.row == ("module", "1.2.3", "1.2.3", "MPY Format")
|
|
431
446
|
|
|
432
447
|
|
|
@@ -806,7 +821,7 @@ def test_get_circuitpython_version():
|
|
|
806
821
|
with mock.patch("circup.logger.warning") as mock_logger:
|
|
807
822
|
backend = DiskBackend("tests/mock_device", mock_logger)
|
|
808
823
|
assert backend.get_circuitpython_version() == (
|
|
809
|
-
"
|
|
824
|
+
"9.0.0",
|
|
810
825
|
"this_is_a_board",
|
|
811
826
|
)
|
|
812
827
|
|
|
@@ -1117,8 +1132,33 @@ def test_show_match_py_command():
|
|
|
1117
1132
|
assert "0 shown" in result.output
|
|
1118
1133
|
|
|
1119
1134
|
|
|
1120
|
-
def
|
|
1135
|
+
def test_imports_from_code():
|
|
1121
1136
|
"""Ensure that various styles of import all work"""
|
|
1137
|
+
test_file = str(pathlib.Path(__file__).parent / "import_styles.py")
|
|
1138
|
+
with open(test_file, "r", encoding="utf8") as fp:
|
|
1139
|
+
test_data = fp.read()
|
|
1140
|
+
|
|
1141
|
+
result = imports_from_code(test_data)
|
|
1142
|
+
print(result)
|
|
1143
|
+
assert result == [
|
|
1144
|
+
"adafruit_bus_device",
|
|
1145
|
+
"adafruit_button",
|
|
1146
|
+
"adafruit_button.Button",
|
|
1147
|
+
"adafruit_display_text",
|
|
1148
|
+
"adafruit_display_text.wrap_text_to_lines",
|
|
1149
|
+
"adafruit_display_text.wrap_text_to_pixels",
|
|
1150
|
+
"adafruit_esp32spi",
|
|
1151
|
+
"adafruit_esp32spi.adafruit_esp32spi_socketpool",
|
|
1152
|
+
"adafruit_hid",
|
|
1153
|
+
"adafruit_hid.consumer_control",
|
|
1154
|
+
"import_styles_sub",
|
|
1155
|
+
"os",
|
|
1156
|
+
"sys",
|
|
1157
|
+
]
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def test_get_all_imports():
|
|
1161
|
+
"""List all libraries from auto file recursively"""
|
|
1122
1162
|
mod_names = [
|
|
1123
1163
|
"adafruit_bus_device",
|
|
1124
1164
|
"adafruit_button",
|
|
@@ -1129,20 +1169,131 @@ def test_libraries_from_imports():
|
|
|
1129
1169
|
"adafruit_oauth2",
|
|
1130
1170
|
"adafruit_requests",
|
|
1131
1171
|
"adafruit_touchscreen",
|
|
1172
|
+
"adafruit_ntp",
|
|
1132
1173
|
]
|
|
1133
|
-
test_file = str(pathlib.Path(__file__).parent / "import_styles.py")
|
|
1134
1174
|
|
|
1135
|
-
|
|
1175
|
+
with mock.patch("circup.logger.info") as mock_logger, mock.patch(
|
|
1176
|
+
"circup.os.path.isfile", return_value=True
|
|
1177
|
+
), mock.patch(
|
|
1178
|
+
"circup.bundle.Bundle.lib_dir",
|
|
1179
|
+
return_value="tests",
|
|
1180
|
+
):
|
|
1181
|
+
tests_dir = pathlib.Path(__file__).parent
|
|
1182
|
+
backend = DiskBackend(tests_dir / "mock_device", mock_logger)
|
|
1183
|
+
|
|
1184
|
+
test_file = str(tests_dir / "import_styles.py")
|
|
1185
|
+
with open(test_file, "r", encoding="utf8") as fp:
|
|
1186
|
+
test_data = fp.read()
|
|
1187
|
+
|
|
1188
|
+
result = get_all_imports(backend, test_data, mod_names, current_module="")
|
|
1189
|
+
|
|
1190
|
+
assert result == [
|
|
1191
|
+
"adafruit_bus_device",
|
|
1192
|
+
"adafruit_button",
|
|
1193
|
+
"adafruit_display_text",
|
|
1194
|
+
"adafruit_esp32spi",
|
|
1195
|
+
"adafruit_hid",
|
|
1196
|
+
"adafruit_ntp",
|
|
1197
|
+
]
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
def test_libraries_from_auto_file_local():
|
|
1201
|
+
"""Check that we get all libraries from auto file argument.
|
|
1202
|
+
Testing here with a local file"""
|
|
1203
|
+
mod_names = [
|
|
1204
|
+
"adafruit_bus_device",
|
|
1205
|
+
"adafruit_button",
|
|
1206
|
+
"adafruit_display_shapes",
|
|
1207
|
+
"adafruit_display_text",
|
|
1208
|
+
"adafruit_esp32spi",
|
|
1209
|
+
"adafruit_hid",
|
|
1210
|
+
"adafruit_oauth2",
|
|
1211
|
+
"adafruit_requests",
|
|
1212
|
+
"adafruit_touchscreen",
|
|
1213
|
+
"adafruit_ntp",
|
|
1214
|
+
]
|
|
1215
|
+
|
|
1216
|
+
auto_file = "./tests/import_styles.py"
|
|
1217
|
+
|
|
1218
|
+
with mock.patch("circup.logger.info") as mock_logger, mock.patch(
|
|
1219
|
+
"circup.os.path.isfile", return_value=True
|
|
1220
|
+
), mock.patch(
|
|
1221
|
+
"circup.bundle.Bundle.lib_dir",
|
|
1222
|
+
return_value="tests",
|
|
1223
|
+
):
|
|
1224
|
+
tests_dir = pathlib.Path(__file__).parent
|
|
1225
|
+
backend = DiskBackend(tests_dir / "mock_device", mock_logger)
|
|
1226
|
+
|
|
1227
|
+
result = libraries_from_auto_file(backend, auto_file, mod_names)
|
|
1228
|
+
|
|
1136
1229
|
assert result == [
|
|
1137
1230
|
"adafruit_bus_device",
|
|
1138
1231
|
"adafruit_button",
|
|
1232
|
+
"adafruit_display_text",
|
|
1139
1233
|
"adafruit_esp32spi",
|
|
1140
1234
|
"adafruit_hid",
|
|
1235
|
+
"adafruit_ntp",
|
|
1236
|
+
]
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def test_libraries_from_auto_file_board():
|
|
1240
|
+
"""Check that we find code.py on the board if we give no auto_file argument"""
|
|
1241
|
+
mod_names = [
|
|
1242
|
+
"adafruit_bus_device",
|
|
1243
|
+
"adafruit_button",
|
|
1244
|
+
"adafruit_display_shapes",
|
|
1245
|
+
"adafruit_display_text",
|
|
1246
|
+
"adafruit_esp32spi",
|
|
1247
|
+
"adafruit_ssd1675",
|
|
1248
|
+
"adafruit_spd1656",
|
|
1249
|
+
"adafruit_spd1608",
|
|
1250
|
+
"adafruit_touchscreen",
|
|
1251
|
+
"adafruit_ntp",
|
|
1252
|
+
]
|
|
1253
|
+
|
|
1254
|
+
auto_file = None
|
|
1255
|
+
|
|
1256
|
+
with mock.patch("circup.logger.info") as mock_logger, mock.patch(
|
|
1257
|
+
"circup.os.path.isfile", return_value=True
|
|
1258
|
+
), mock.patch(
|
|
1259
|
+
"circup.bundle.Bundle.lib_dir",
|
|
1260
|
+
return_value="tests",
|
|
1261
|
+
):
|
|
1262
|
+
tests_dir = pathlib.Path(__file__).parent
|
|
1263
|
+
backend = DiskBackend(tests_dir / "mock_device_2", mock_logger)
|
|
1264
|
+
|
|
1265
|
+
result = libraries_from_auto_file(backend, auto_file, mod_names)
|
|
1266
|
+
|
|
1267
|
+
assert result == [
|
|
1268
|
+
"adafruit_spd1608",
|
|
1269
|
+
"adafruit_spd1656",
|
|
1270
|
+
"adafruit_ssd1675",
|
|
1141
1271
|
]
|
|
1142
1272
|
|
|
1143
1273
|
|
|
1144
|
-
def
|
|
1145
|
-
"""
|
|
1274
|
+
def test_libraries_from_auto_file_none():
|
|
1275
|
+
"""Check that we exit if we give no auto_file argument
|
|
1276
|
+
and there's no default code file"""
|
|
1277
|
+
mod_names = []
|
|
1278
|
+
auto_file = None
|
|
1279
|
+
|
|
1280
|
+
with mock.patch("circup.logger.info") as mock_logger, mock.patch(
|
|
1281
|
+
"circup.os.path.isfile", return_value=True
|
|
1282
|
+
), mock.patch(
|
|
1283
|
+
"circup.bundle.Bundle.lib_dir",
|
|
1284
|
+
return_value="tests",
|
|
1285
|
+
):
|
|
1286
|
+
tests_dir = pathlib.Path(__file__).parent
|
|
1287
|
+
backend = DiskBackend(tests_dir / "mock_device", mock_logger)
|
|
1288
|
+
try:
|
|
1289
|
+
libraries_from_auto_file(backend, auto_file, mod_names)
|
|
1290
|
+
raise Exception("Did not call exit")
|
|
1291
|
+
except SystemExit as ex:
|
|
1292
|
+
assert ex.code == 1
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
def test_install_auto_file_bad():
|
|
1296
|
+
"""Ensure that we catch an error when parsing auto file"""
|
|
1146
1297
|
TEST_BUNDLE_MODULES = {"one.py": {}, "two.py": {}, "three.py": {}}
|
|
1147
1298
|
runner = CliRunner()
|
|
1148
1299
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|