circup 1.7.0__tar.gz → 1.8.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-1.7.0/circup.egg-info → circup-1.8.0}/PKG-INFO +8 -5
- {circup-1.7.0 → circup-1.8.0}/README.rst +3 -0
- {circup-1.7.0 → circup-1.8.0}/circup/backends.py +75 -23
- {circup-1.7.0 → circup-1.8.0}/circup/bundle.py +14 -0
- {circup-1.7.0 → circup-1.8.0}/circup/command_utils.py +54 -0
- {circup-1.7.0 → circup-1.8.0}/circup/commands.py +53 -2
- {circup-1.7.0 → circup-1.8.0/circup.egg-info}/PKG-INFO +8 -5
- {circup-1.7.0 → circup-1.8.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-1.7.0 → circup-1.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-1.7.0 → circup-1.8.0}/.github/workflows/build.yml +0 -0
- {circup-1.7.0 → circup-1.8.0}/.github/workflows/release.yml +0 -0
- {circup-1.7.0 → circup-1.8.0}/.gitignore +0 -0
- {circup-1.7.0 → circup-1.8.0}/.isort.cfg +0 -0
- {circup-1.7.0 → circup-1.8.0}/.pre-commit-config.yaml +0 -0
- {circup-1.7.0 → circup-1.8.0}/.pylintrc +0 -0
- {circup-1.7.0 → circup-1.8.0}/CODE_OF_CONDUCT.rst +0 -0
- {circup-1.7.0 → circup-1.8.0}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/CONTRIBUTING.rst +0 -0
- {circup-1.7.0 → circup-1.8.0}/CONTRIBUTING.rst.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/LICENSE +0 -0
- {circup-1.7.0 → circup-1.8.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/LICENSES/MIT.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/LICENSES/Unlicense.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/README.rst.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/__init__.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/config/bundle_config.json +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/config/bundle_config.json.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/logging.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/module.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup/shared.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup.egg-info/SOURCES.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup.egg-info/dependency_links.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup.egg-info/entry_points.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/circup.egg-info/requires.txt +4 -4
- {circup-1.7.0 → circup-1.8.0}/circup.egg-info/top_level.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/_static/favicon.ico +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/_static/favicon.ico.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/conf.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/index.rst +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/index.rst.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/logo.png +0 -0
- {circup-1.7.0 → circup-1.8.0}/docs/logo.png.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/optional_requirements.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/optional_requirements.txt.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/readthedocs.yml +0 -0
- {circup-1.7.0 → circup-1.8.0}/requirements.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/requirements.txt.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/setup.cfg +0 -0
- {circup-1.7.0 → circup-1.8.0}/setup.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/__init__.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/bad_module/__init__.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/bad_module/my_module.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/bad_python.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/bundle.json +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/bundle.json.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/device.json +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/device.json.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/dir_module/__init__.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/dir_module/my_module.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/import_styles.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/local_module.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/local_module_cp7.mpy +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/local_module_cp7.mpy.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mock_device/boot_out.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mount_exists.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mount_exists.txt.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mount_missing.txt +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/mount_missing.txt.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/remote_module.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_bundle_config.json +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_bundle_config.json.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_bundle_config_local.json +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_bundle_config_local.json.license +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_circup.py +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_module.mpy +0 -0
- {circup-1.7.0 → circup-1.8.0}/tests/test_module.mpy.license +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Home-page: https://github.com/adafruit/circup
|
|
6
6
|
Author: Adafruit Industries
|
|
@@ -59,14 +59,14 @@ Requires-Dist: twine; extra == "dev"
|
|
|
59
59
|
Provides-Extra: all
|
|
60
60
|
Requires-Dist: wheel; extra == "all"
|
|
61
61
|
Requires-Dist: pytest-faulthandler; extra == "all"
|
|
62
|
-
Requires-Dist:
|
|
62
|
+
Requires-Dist: coverage; extra == "all"
|
|
63
63
|
Requires-Dist: pylint; extra == "all"
|
|
64
64
|
Requires-Dist: pytest; extra == "all"
|
|
65
|
-
Requires-Dist: coverage; extra == "all"
|
|
66
|
-
Requires-Dist: black; extra == "all"
|
|
67
|
-
Requires-Dist: pytest-cov; extra == "all"
|
|
68
65
|
Requires-Dist: twine; extra == "all"
|
|
69
66
|
Requires-Dist: sphinx; extra == "all"
|
|
67
|
+
Requires-Dist: black; extra == "all"
|
|
68
|
+
Requires-Dist: pytest-random-order>=1.0.0; extra == "all"
|
|
69
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
CircUp
|
|
@@ -165,6 +165,8 @@ To get help, just type the command::
|
|
|
165
165
|
--host TEXT Hostname or IP address of a device. Overrides automatic
|
|
166
166
|
path detection.
|
|
167
167
|
--password TEXT Password to use for authentication when --host is used.
|
|
168
|
+
--timeout INTEGER Specify the timeout in seconds for any network
|
|
169
|
+
operations.
|
|
168
170
|
--board-id TEXT Manual Board ID of the CircuitPython device. If provided
|
|
169
171
|
in combination with --cpy-version, it overrides the
|
|
170
172
|
detected board ID.
|
|
@@ -177,6 +179,7 @@ To get help, just type the command::
|
|
|
177
179
|
bundle-add Add bundles to the local bundles list, by "user/repo"...
|
|
178
180
|
bundle-remove Remove one or more bundles from the local bundles list.
|
|
179
181
|
bundle-show Show the list of bundles, default and local, with URL,...
|
|
182
|
+
example Copy named example(s) from a bundle onto the device.
|
|
180
183
|
freeze Output details of all the modules found on the connected...
|
|
181
184
|
install Install a named module(s) onto the device.
|
|
182
185
|
list Lists all out of date modules found on the connected...
|
|
@@ -95,6 +95,8 @@ To get help, just type the command::
|
|
|
95
95
|
--host TEXT Hostname or IP address of a device. Overrides automatic
|
|
96
96
|
path detection.
|
|
97
97
|
--password TEXT Password to use for authentication when --host is used.
|
|
98
|
+
--timeout INTEGER Specify the timeout in seconds for any network
|
|
99
|
+
operations.
|
|
98
100
|
--board-id TEXT Manual Board ID of the CircuitPython device. If provided
|
|
99
101
|
in combination with --cpy-version, it overrides the
|
|
100
102
|
detected board ID.
|
|
@@ -107,6 +109,7 @@ To get help, just type the command::
|
|
|
107
109
|
bundle-add Add bundles to the local bundles list, by "user/repo"...
|
|
108
110
|
bundle-remove Remove one or more bundles from the local bundles list.
|
|
109
111
|
bundle-show Show the list of bundles, default and local, with URL,...
|
|
112
|
+
example Copy named example(s) from a bundle onto the device.
|
|
110
113
|
freeze Output details of all the modules found on the connected...
|
|
111
114
|
install Install a named module(s) onto the device.
|
|
112
115
|
list Lists all out of date modules found on the connected...
|
|
@@ -16,7 +16,6 @@ import requests
|
|
|
16
16
|
from requests.adapters import HTTPAdapter
|
|
17
17
|
from requests.auth import HTTPBasicAuth
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
from circup.shared import DATA_DIR, BAD_FILE_FORMAT, extract_metadata, _get_modules_file
|
|
21
20
|
|
|
22
21
|
#: The location to store a local copy of code.py for use with --auto and
|
|
@@ -79,13 +78,13 @@ class Backend:
|
|
|
79
78
|
"""
|
|
80
79
|
raise NotImplementedError
|
|
81
80
|
|
|
82
|
-
def
|
|
81
|
+
def install_module_py(self, metadata, location=None):
|
|
83
82
|
"""
|
|
84
83
|
To be overridden by subclass
|
|
85
84
|
"""
|
|
86
85
|
raise NotImplementedError
|
|
87
86
|
|
|
88
|
-
def
|
|
87
|
+
def install_module_mpy(self, bundle, metadata):
|
|
89
88
|
"""
|
|
90
89
|
To be overridden by subclass
|
|
91
90
|
"""
|
|
@@ -93,7 +92,7 @@ class Backend:
|
|
|
93
92
|
|
|
94
93
|
# pylint: disable=too-many-locals,too-many-branches,too-many-arguments,too-many-nested-blocks
|
|
95
94
|
def install_module(
|
|
96
|
-
self, device_path, device_modules, name, pyext, mod_names
|
|
95
|
+
self, device_path, device_modules, name, pyext, mod_names, upgrade=False
|
|
97
96
|
): # pragma: no cover
|
|
98
97
|
"""
|
|
99
98
|
Finds a connected device and installs a given module name if it
|
|
@@ -108,14 +107,27 @@ class Backend:
|
|
|
108
107
|
source or from a pre-compiled module
|
|
109
108
|
:param mod_names: Dictionary of metadata from modules that can be generated
|
|
110
109
|
with get_bundle_versions()
|
|
110
|
+
:param bool upgrade: Upgrade the specified modules if they're already installed.
|
|
111
111
|
"""
|
|
112
112
|
if not name:
|
|
113
113
|
click.echo("No module name(s) provided.")
|
|
114
114
|
elif name in mod_names:
|
|
115
115
|
# Grab device modules to check if module already installed
|
|
116
116
|
if name in device_modules:
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
if not upgrade:
|
|
118
|
+
# skip already installed modules if no -upgrade flag
|
|
119
|
+
click.echo("'{}' is already installed.".format(name))
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# uninstall the module before installing
|
|
123
|
+
name = name.lower()
|
|
124
|
+
_mod_names = {}
|
|
125
|
+
for module_item, _metadata in device_modules.items():
|
|
126
|
+
_mod_names[module_item.replace(".py", "").lower()] = _metadata
|
|
127
|
+
if name in _mod_names:
|
|
128
|
+
_metadata = _mod_names[name]
|
|
129
|
+
module_path = _metadata["path"]
|
|
130
|
+
self.uninstall(device_path, module_path)
|
|
119
131
|
|
|
120
132
|
library_path = (
|
|
121
133
|
os.path.join(device_path, self.LIB_DIR_PATH)
|
|
@@ -159,10 +171,10 @@ class Backend:
|
|
|
159
171
|
|
|
160
172
|
if pyext:
|
|
161
173
|
# Use Python source for module.
|
|
162
|
-
self.
|
|
174
|
+
self.install_module_py(metadata)
|
|
163
175
|
else:
|
|
164
176
|
# Use pre-compiled mpy modules.
|
|
165
|
-
self.
|
|
177
|
+
self.install_module_mpy(bundle, metadata)
|
|
166
178
|
click.echo("Installed '{}'.".format(name))
|
|
167
179
|
else:
|
|
168
180
|
click.echo("Unknown module named, '{}'.".format(name))
|
|
@@ -219,6 +231,12 @@ class Backend:
|
|
|
219
231
|
board_id = ""
|
|
220
232
|
return circuit_python, board_id
|
|
221
233
|
|
|
234
|
+
def file_exists(self, filepath):
|
|
235
|
+
"""
|
|
236
|
+
To be overriden by subclass
|
|
237
|
+
"""
|
|
238
|
+
raise NotImplementedError
|
|
239
|
+
|
|
222
240
|
|
|
223
241
|
def _writeable_error():
|
|
224
242
|
click.secho(
|
|
@@ -250,7 +268,8 @@ class WebBackend(Backend):
|
|
|
250
268
|
else "Could not find or connect to specified device"
|
|
251
269
|
) from exc
|
|
252
270
|
|
|
253
|
-
self.
|
|
271
|
+
self.FS_PATH = "fs/"
|
|
272
|
+
self.LIB_DIR_PATH = f"{self.FS_PATH}lib/"
|
|
254
273
|
self.host = host
|
|
255
274
|
self.password = password
|
|
256
275
|
self.device_location = f"http://:{self.password}@{self.host}"
|
|
@@ -260,14 +279,20 @@ class WebBackend(Backend):
|
|
|
260
279
|
self.library_path = self.device_location + "/" + self.LIB_DIR_PATH
|
|
261
280
|
self.timeout = timeout
|
|
262
281
|
|
|
263
|
-
def install_file_http(self, source):
|
|
282
|
+
def install_file_http(self, source, location=None):
|
|
264
283
|
"""
|
|
265
284
|
Install file to device using web workflow.
|
|
266
285
|
:param source source file.
|
|
286
|
+
:param location the location on the device to copy the source
|
|
287
|
+
directory in to. If omitted is CIRCUITPY/lib/ used.
|
|
267
288
|
"""
|
|
268
289
|
file_name = source.split(os.path.sep)
|
|
269
290
|
file_name = file_name[-2] if file_name[-1] == "" else file_name[-1]
|
|
270
|
-
|
|
291
|
+
|
|
292
|
+
if location is None:
|
|
293
|
+
target = self.device_location + "/" + self.LIB_DIR_PATH + file_name
|
|
294
|
+
else:
|
|
295
|
+
target = self.device_location + "/" + self.FS_PATH + location + file_name
|
|
271
296
|
|
|
272
297
|
auth = HTTPBasicAuth("", self.password)
|
|
273
298
|
|
|
@@ -277,14 +302,19 @@ class WebBackend(Backend):
|
|
|
277
302
|
_writeable_error()
|
|
278
303
|
r.raise_for_status()
|
|
279
304
|
|
|
280
|
-
def install_dir_http(self, source):
|
|
305
|
+
def install_dir_http(self, source, location=None):
|
|
281
306
|
"""
|
|
282
307
|
Install directory to device using web workflow.
|
|
283
308
|
:param source source directory.
|
|
309
|
+
:param location the location on the device to copy the source
|
|
310
|
+
directory in to. If omitted is CIRCUITPY/lib/ used.
|
|
284
311
|
"""
|
|
285
312
|
mod_name = source.split(os.path.sep)
|
|
286
313
|
mod_name = mod_name[-2] if mod_name[-1] == "" else mod_name[-1]
|
|
287
|
-
|
|
314
|
+
if location is None:
|
|
315
|
+
target = self.device_location + "/" + self.LIB_DIR_PATH + mod_name
|
|
316
|
+
else:
|
|
317
|
+
target = self.device_location + "/" + self.FS_PATH + location + mod_name
|
|
288
318
|
target = target + "/" if target[:-1] != "/" else target
|
|
289
319
|
url = urlparse(target)
|
|
290
320
|
auth = HTTPBasicAuth("", url.password)
|
|
@@ -490,7 +520,7 @@ class WebBackend(Backend):
|
|
|
490
520
|
_writeable_error()
|
|
491
521
|
r.raise_for_status()
|
|
492
522
|
|
|
493
|
-
def
|
|
523
|
+
def install_module_mpy(self, bundle, metadata):
|
|
494
524
|
"""
|
|
495
525
|
:param bundle library bundle.
|
|
496
526
|
:param library_path library path
|
|
@@ -514,7 +544,7 @@ class WebBackend(Backend):
|
|
|
514
544
|
raise IOError("Cannot find compiled version of module.")
|
|
515
545
|
|
|
516
546
|
# pylint: enable=too-many-locals,too-many-branches
|
|
517
|
-
def
|
|
547
|
+
def install_module_py(self, metadata, location=None):
|
|
518
548
|
"""
|
|
519
549
|
:param library_path library path
|
|
520
550
|
:param metadata dictionary.
|
|
@@ -522,10 +552,10 @@ class WebBackend(Backend):
|
|
|
522
552
|
|
|
523
553
|
source_path = metadata["path"] # Path to Python source version.
|
|
524
554
|
if os.path.isdir(source_path):
|
|
525
|
-
self.install_dir_http(source_path)
|
|
555
|
+
self.install_dir_http(source_path, location=location)
|
|
526
556
|
|
|
527
557
|
else:
|
|
528
|
-
self.install_file_http(source_path)
|
|
558
|
+
self.install_file_http(source_path, location=location)
|
|
529
559
|
|
|
530
560
|
def get_auto_file_path(self, auto_file_path):
|
|
531
561
|
"""
|
|
@@ -560,6 +590,18 @@ class WebBackend(Backend):
|
|
|
560
590
|
"""
|
|
561
591
|
self._update_http(module)
|
|
562
592
|
|
|
593
|
+
def file_exists(self, filepath):
|
|
594
|
+
"""
|
|
595
|
+
return True if the file exists, otherwise False.
|
|
596
|
+
"""
|
|
597
|
+
auth = HTTPBasicAuth("", self.password)
|
|
598
|
+
resp = requests.get(
|
|
599
|
+
self.get_file_path(filepath), auth=auth, timeout=self.timeout
|
|
600
|
+
)
|
|
601
|
+
if resp.status_code == 200:
|
|
602
|
+
return True
|
|
603
|
+
return False
|
|
604
|
+
|
|
563
605
|
def _update_http(self, module):
|
|
564
606
|
"""
|
|
565
607
|
Update the module using web workflow.
|
|
@@ -733,10 +775,9 @@ class DiskBackend(Backend):
|
|
|
733
775
|
if not os.path.exists(library_path): # pragma: no cover
|
|
734
776
|
os.makedirs(library_path)
|
|
735
777
|
|
|
736
|
-
def
|
|
778
|
+
def install_module_mpy(self, bundle, metadata):
|
|
737
779
|
"""
|
|
738
780
|
:param bundle library bundle.
|
|
739
|
-
:param library_path library path
|
|
740
781
|
:param metadata dictionary.
|
|
741
782
|
"""
|
|
742
783
|
module_name = os.path.basename(metadata["path"]).replace(".py", ".mpy")
|
|
@@ -763,21 +804,26 @@ class DiskBackend(Backend):
|
|
|
763
804
|
raise IOError("Cannot find compiled version of module.")
|
|
764
805
|
|
|
765
806
|
# pylint: enable=too-many-locals,too-many-branches
|
|
766
|
-
def
|
|
807
|
+
def install_module_py(self, metadata, location=None):
|
|
767
808
|
"""
|
|
768
|
-
:param library_path library path
|
|
769
809
|
:param metadata dictionary.
|
|
810
|
+
:param location the location on the device to copy the py module to.
|
|
811
|
+
If omitted is CIRCUITPY/lib/ used.
|
|
770
812
|
"""
|
|
813
|
+
if location is None:
|
|
814
|
+
location = self.library_path
|
|
815
|
+
else:
|
|
816
|
+
location = os.path.join(self.device_location, location)
|
|
771
817
|
|
|
772
818
|
source_path = metadata["path"] # Path to Python source version.
|
|
773
819
|
if os.path.isdir(source_path):
|
|
774
820
|
target = os.path.basename(os.path.dirname(source_path))
|
|
775
|
-
target_path = os.path.join(
|
|
821
|
+
target_path = os.path.join(location, target)
|
|
776
822
|
# Copy the directory.
|
|
777
823
|
shutil.copytree(source_path, target_path)
|
|
778
824
|
else:
|
|
779
825
|
target = os.path.basename(source_path)
|
|
780
|
-
target_path = os.path.join(
|
|
826
|
+
target_path = os.path.join(location, target)
|
|
781
827
|
# Copy file.
|
|
782
828
|
shutil.copyfile(source_path, target_path)
|
|
783
829
|
|
|
@@ -825,6 +871,12 @@ class DiskBackend(Backend):
|
|
|
825
871
|
os.remove(module.path)
|
|
826
872
|
shutil.copyfile(module.bundle_path, module.path)
|
|
827
873
|
|
|
874
|
+
def file_exists(self, filepath):
|
|
875
|
+
"""
|
|
876
|
+
return True if the file exists, otherwise False.
|
|
877
|
+
"""
|
|
878
|
+
return os.path.exists(os.path.join(self.device_location, filepath))
|
|
879
|
+
|
|
828
880
|
def get_file_path(self, filename):
|
|
829
881
|
"""
|
|
830
882
|
returns the full path on the device to a given file name.
|
|
@@ -61,6 +61,20 @@ class Bundle:
|
|
|
61
61
|
"lib",
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
+
def examples_dir(self, platform):
|
|
65
|
+
"""
|
|
66
|
+
This bundle's examples directory for the platform.
|
|
67
|
+
|
|
68
|
+
:param str platform: The platform identifier (py/6mpy/...).
|
|
69
|
+
:return: The path to the examples directory for the platform.
|
|
70
|
+
"""
|
|
71
|
+
tag = self.current_tag
|
|
72
|
+
return os.path.join(
|
|
73
|
+
self.dir.format(platform=platform),
|
|
74
|
+
self.basename.format(platform=PLATFORMS[platform], tag=tag),
|
|
75
|
+
"examples",
|
|
76
|
+
)
|
|
77
|
+
|
|
64
78
|
def requirements_for(self, library_name, toml_file=False):
|
|
65
79
|
"""
|
|
66
80
|
The requirements file for this library.
|
|
@@ -93,6 +93,23 @@ def completion_for_install(ctx, param, incomplete):
|
|
|
93
93
|
return sorted(module_names)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def completion_for_example(ctx, param, incomplete):
|
|
97
|
+
"""
|
|
98
|
+
Returns the list of available modules for the command line tab-completion
|
|
99
|
+
with the ``circup example`` command.
|
|
100
|
+
"""
|
|
101
|
+
# pylint: disable=unused-argument, consider-iterating-dictionary
|
|
102
|
+
available_examples = get_bundle_examples(get_bundles_list(), avoid_download=True)
|
|
103
|
+
|
|
104
|
+
matching_examples = [
|
|
105
|
+
example_path
|
|
106
|
+
for example_path in available_examples.keys()
|
|
107
|
+
if example_path.startswith(incomplete)
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
return sorted(matching_examples)
|
|
111
|
+
|
|
112
|
+
|
|
96
113
|
def ensure_latest_bundle(bundle):
|
|
97
114
|
"""
|
|
98
115
|
Ensure that there's a copy of the latest library bundle available so circup
|
|
@@ -290,6 +307,43 @@ def get_bundle(bundle, tag):
|
|
|
290
307
|
click.echo("\nOK\n")
|
|
291
308
|
|
|
292
309
|
|
|
310
|
+
def get_bundle_examples(bundles_list, avoid_download=False):
|
|
311
|
+
"""
|
|
312
|
+
Return a dictionary of metadata from examples in the all of the bundles
|
|
313
|
+
specified by bundles_list argument.
|
|
314
|
+
|
|
315
|
+
:param List[Bundle] bundles_list: List of supported bundles as Bundle objects.
|
|
316
|
+
:param bool avoid_download: if True, download the bundle only if missing.
|
|
317
|
+
:return: A dictionary of metadata about the examples available in the
|
|
318
|
+
library bundle.
|
|
319
|
+
"""
|
|
320
|
+
# pylint: disable=too-many-nested-blocks
|
|
321
|
+
all_the_examples = dict()
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
for bundle in bundles_list:
|
|
325
|
+
if not avoid_download or not os.path.isdir(bundle.lib_dir("py")):
|
|
326
|
+
ensure_latest_bundle(bundle)
|
|
327
|
+
path = bundle.examples_dir("py")
|
|
328
|
+
path_examples = _get_modules_file(path, logger)
|
|
329
|
+
for lib_name, lib_metadata in path_examples.items():
|
|
330
|
+
for _dir_level in os.walk(lib_metadata["path"]):
|
|
331
|
+
for _file in _dir_level[2]:
|
|
332
|
+
_parts = _dir_level[0].split(os.path.sep)
|
|
333
|
+
_lib_name_index = _parts.index(lib_name)
|
|
334
|
+
_dirs = _parts[_lib_name_index:]
|
|
335
|
+
if _dirs[-1] == "":
|
|
336
|
+
_dirs.pop(-1)
|
|
337
|
+
slug = f"{os.path.sep}".join(_dirs + [_file.replace(".py", "")])
|
|
338
|
+
all_the_examples[slug] = os.path.join(_dir_level[0], _file)
|
|
339
|
+
|
|
340
|
+
except NotADirectoryError:
|
|
341
|
+
# Bundle does not have new style examples directory
|
|
342
|
+
# so we cannot include its examples.
|
|
343
|
+
pass
|
|
344
|
+
return all_the_examples
|
|
345
|
+
|
|
346
|
+
|
|
293
347
|
def get_bundle_versions(bundles_list, avoid_download=False):
|
|
294
348
|
"""
|
|
295
349
|
Returns a dictionary of metadata from modules in the latest known release
|
|
@@ -38,6 +38,8 @@ from circup.command_utils import (
|
|
|
38
38
|
get_bundles_local_dict,
|
|
39
39
|
save_local_bundles,
|
|
40
40
|
get_bundles_dict,
|
|
41
|
+
completion_for_example,
|
|
42
|
+
get_bundle_examples,
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
|
|
@@ -284,6 +286,9 @@ def list_cli(ctx): # pragma: no cover
|
|
|
284
286
|
@click.option(
|
|
285
287
|
"--auto", "-a", is_flag=True, help="Install the modules imported in code.py."
|
|
286
288
|
)
|
|
289
|
+
@click.option(
|
|
290
|
+
"--upgrade", "-U", is_flag=True, help="Upgrade modules that are already installed."
|
|
291
|
+
)
|
|
287
292
|
@click.option(
|
|
288
293
|
"--auto-file",
|
|
289
294
|
default=None,
|
|
@@ -291,7 +296,9 @@ def list_cli(ctx): # pragma: no cover
|
|
|
291
296
|
" Also accepts an absolute path or a local ./ path.",
|
|
292
297
|
)
|
|
293
298
|
@click.pass_context
|
|
294
|
-
def install(
|
|
299
|
+
def install(
|
|
300
|
+
ctx, modules, pyext, requirement, auto, auto_file, upgrade=False
|
|
301
|
+
): # pragma: no cover
|
|
295
302
|
"""
|
|
296
303
|
Install a named module(s) onto the device. Multiple modules
|
|
297
304
|
can be installed at once by providing more than one module name, each
|
|
@@ -343,7 +350,51 @@ def install(ctx, modules, pyext, requirement, auto, auto_file): # pragma: no co
|
|
|
343
350
|
click.echo(f"Ready to install: {to_install}\n")
|
|
344
351
|
for library in to_install:
|
|
345
352
|
ctx.obj["backend"].install_module(
|
|
346
|
-
ctx.obj["DEVICE_PATH"],
|
|
353
|
+
ctx.obj["DEVICE_PATH"],
|
|
354
|
+
device_modules,
|
|
355
|
+
library,
|
|
356
|
+
pyext,
|
|
357
|
+
mod_names,
|
|
358
|
+
upgrade,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@main.command()
|
|
363
|
+
@click.option("--overwrite", is_flag=True, help="Overwrite the file if it exists.")
|
|
364
|
+
@click.argument(
|
|
365
|
+
"examples", required=True, nargs=-1, shell_complete=completion_for_example
|
|
366
|
+
)
|
|
367
|
+
@click.pass_context
|
|
368
|
+
def example(ctx, examples, overwrite):
|
|
369
|
+
"""
|
|
370
|
+
Copy named example(s) from a bundle onto the device. Multiple examples
|
|
371
|
+
can be installed at once by providing more than one example name, each
|
|
372
|
+
separated by a space.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
for example_arg in examples:
|
|
376
|
+
available_examples = get_bundle_examples(
|
|
377
|
+
get_bundles_list(), avoid_download=True
|
|
378
|
+
)
|
|
379
|
+
if example_arg in available_examples:
|
|
380
|
+
filename = available_examples[example_arg].split(os.path.sep)[-1]
|
|
381
|
+
|
|
382
|
+
if overwrite or not ctx.obj["backend"].file_exists(filename):
|
|
383
|
+
click.echo(
|
|
384
|
+
f"{'Copying' if not overwrite else 'Overwriting'}: {filename}"
|
|
385
|
+
)
|
|
386
|
+
ctx.obj["backend"].install_module_py(
|
|
387
|
+
{"path": available_examples[example_arg]}, location=""
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
click.secho(
|
|
391
|
+
f"File: {filename} already exists. Use --overwrite if you wish to replace it.",
|
|
392
|
+
fg="red",
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
click.secho(
|
|
396
|
+
f"Error: {example_arg} was not found in any local bundle examples.",
|
|
397
|
+
fg="red",
|
|
347
398
|
)
|
|
348
399
|
|
|
349
400
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Home-page: https://github.com/adafruit/circup
|
|
6
6
|
Author: Adafruit Industries
|
|
@@ -59,14 +59,14 @@ Requires-Dist: twine; extra == "dev"
|
|
|
59
59
|
Provides-Extra: all
|
|
60
60
|
Requires-Dist: wheel; extra == "all"
|
|
61
61
|
Requires-Dist: pytest-faulthandler; extra == "all"
|
|
62
|
-
Requires-Dist:
|
|
62
|
+
Requires-Dist: coverage; extra == "all"
|
|
63
63
|
Requires-Dist: pylint; extra == "all"
|
|
64
64
|
Requires-Dist: pytest; extra == "all"
|
|
65
|
-
Requires-Dist: coverage; extra == "all"
|
|
66
|
-
Requires-Dist: black; extra == "all"
|
|
67
|
-
Requires-Dist: pytest-cov; extra == "all"
|
|
68
65
|
Requires-Dist: twine; extra == "all"
|
|
69
66
|
Requires-Dist: sphinx; extra == "all"
|
|
67
|
+
Requires-Dist: black; extra == "all"
|
|
68
|
+
Requires-Dist: pytest-random-order>=1.0.0; extra == "all"
|
|
69
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
CircUp
|
|
@@ -165,6 +165,8 @@ To get help, just type the command::
|
|
|
165
165
|
--host TEXT Hostname or IP address of a device. Overrides automatic
|
|
166
166
|
path detection.
|
|
167
167
|
--password TEXT Password to use for authentication when --host is used.
|
|
168
|
+
--timeout INTEGER Specify the timeout in seconds for any network
|
|
169
|
+
operations.
|
|
168
170
|
--board-id TEXT Manual Board ID of the CircuitPython device. If provided
|
|
169
171
|
in combination with --cpy-version, it overrides the
|
|
170
172
|
detected board ID.
|
|
@@ -177,6 +179,7 @@ To get help, just type the command::
|
|
|
177
179
|
bundle-add Add bundles to the local bundles list, by "user/repo"...
|
|
178
180
|
bundle-remove Remove one or more bundles from the local bundles list.
|
|
179
181
|
bundle-show Show the list of bundles, default and local, with URL,...
|
|
182
|
+
example Copy named example(s) from a bundle onto the device.
|
|
180
183
|
freeze Output details of all the modules found on the connected...
|
|
181
184
|
install Install a named module(s) onto the device.
|
|
182
185
|
list Lists all out of date modules found on the connected...
|
|
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
|
|
@@ -12,14 +12,14 @@ importlib_metadata
|
|
|
12
12
|
[all]
|
|
13
13
|
wheel
|
|
14
14
|
pytest-faulthandler
|
|
15
|
-
|
|
15
|
+
coverage
|
|
16
16
|
pylint
|
|
17
17
|
pytest
|
|
18
|
-
coverage
|
|
19
|
-
black
|
|
20
|
-
pytest-cov
|
|
21
18
|
twine
|
|
22
19
|
sphinx
|
|
20
|
+
black
|
|
21
|
+
pytest-random-order>=1.0.0
|
|
22
|
+
pytest-cov
|
|
23
23
|
|
|
24
24
|
[dev]
|
|
25
25
|
pytest
|
|
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
|