circup 1.6.2__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.6.2 → circup-1.8.0}/.gitignore +2 -0
- {circup-1.6.2/circup.egg-info → circup-1.8.0}/PKG-INFO +11 -8
- {circup-1.6.2 → circup-1.8.0}/README.rst +3 -0
- circup-1.8.0/circup/__init__.py +26 -0
- {circup-1.6.2 → circup-1.8.0}/circup/backends.py +75 -23
- circup-1.8.0/circup/bundle.py +170 -0
- circup-1.8.0/circup/command_utils.py +616 -0
- circup-1.8.0/circup/commands.py +708 -0
- circup-1.8.0/circup/logging.py +33 -0
- circup-1.8.0/circup/module.py +209 -0
- {circup-1.6.2 → circup-1.8.0}/circup/shared.py +75 -1
- {circup-1.6.2 → circup-1.8.0/circup.egg-info}/PKG-INFO +11 -8
- {circup-1.6.2 → circup-1.8.0}/circup.egg-info/SOURCES.txt +9 -1
- {circup-1.6.2 → circup-1.8.0}/docs/conf.py +1 -1
- circup-1.8.0/optional_requirements.txt +4 -0
- circup-1.8.0/optional_requirements.txt.license +3 -0
- circup-1.8.0/requirements.txt +7 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mock_device/boot_out.txt +1 -1
- circup-1.8.0/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_circup.py +198 -197
- circup-1.6.2/circup/__init__.py +0 -1584
- circup-1.6.2/requirements.txt +0 -58
- {circup-1.6.2 → circup-1.8.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-1.6.2 → circup-1.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-1.6.2 → circup-1.8.0}/.github/workflows/build.yml +0 -0
- {circup-1.6.2 → circup-1.8.0}/.github/workflows/release.yml +0 -0
- {circup-1.6.2 → circup-1.8.0}/.isort.cfg +0 -0
- {circup-1.6.2 → circup-1.8.0}/.pre-commit-config.yaml +0 -0
- {circup-1.6.2 → circup-1.8.0}/.pylintrc +0 -0
- {circup-1.6.2 → circup-1.8.0}/CODE_OF_CONDUCT.rst +0 -0
- {circup-1.6.2 → circup-1.8.0}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/CONTRIBUTING.rst +0 -0
- {circup-1.6.2 → circup-1.8.0}/CONTRIBUTING.rst.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/LICENSE +0 -0
- {circup-1.6.2 → circup-1.8.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/LICENSES/MIT.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/LICENSES/Unlicense.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/README.rst.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/circup/config/bundle_config.json +0 -0
- {circup-1.6.2 → circup-1.8.0}/circup/config/bundle_config.json.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/circup.egg-info/dependency_links.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/circup.egg-info/entry_points.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/circup.egg-info/requires.txt +7 -7
- {circup-1.6.2 → circup-1.8.0}/circup.egg-info/top_level.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/_static/favicon.ico +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/_static/favicon.ico.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/index.rst +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/index.rst.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/logo.png +0 -0
- {circup-1.6.2 → circup-1.8.0}/docs/logo.png.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/readthedocs.yml +0 -0
- {circup-1.6.2 → circup-1.8.0}/requirements.txt.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/setup.cfg +0 -0
- {circup-1.6.2 → circup-1.8.0}/setup.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/__init__.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/bad_module/__init__.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/bad_module/my_module.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/bad_python.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/bundle.json +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/bundle.json.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/device.json +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/device.json.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/dir_module/__init__.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/dir_module/my_module.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/import_styles.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/local_module.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/local_module_cp7.mpy +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/local_module_cp7.mpy.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mount_exists.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mount_exists.txt.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mount_missing.txt +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/mount_missing.txt.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/remote_module.py +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_bundle_config.json +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_bundle_config.json.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_bundle_config_local.json +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_bundle_config_local.json.license +0 -0
- {circup-1.6.2 → circup-1.8.0}/tests/test_module.mpy +0 -0
- {circup-1.6.2 → 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
|
|
@@ -57,16 +57,16 @@ Requires-Dist: sphinx; extra == "dev"
|
|
|
57
57
|
Requires-Dist: wheel; extra == "dev"
|
|
58
58
|
Requires-Dist: twine; extra == "dev"
|
|
59
59
|
Provides-Extra: all
|
|
60
|
-
Requires-Dist: black; extra == "all"
|
|
61
|
-
Requires-Dist: pytest-cov; extra == "all"
|
|
62
|
-
Requires-Dist: pytest-random-order>=1.0.0; extra == "all"
|
|
63
|
-
Requires-Dist: sphinx; extra == "all"
|
|
64
|
-
Requires-Dist: coverage; extra == "all"
|
|
65
|
-
Requires-Dist: twine; extra == "all"
|
|
66
60
|
Requires-Dist: wheel; extra == "all"
|
|
67
61
|
Requires-Dist: pytest-faulthandler; extra == "all"
|
|
68
|
-
Requires-Dist:
|
|
62
|
+
Requires-Dist: coverage; extra == "all"
|
|
69
63
|
Requires-Dist: pylint; extra == "all"
|
|
64
|
+
Requires-Dist: pytest; extra == "all"
|
|
65
|
+
Requires-Dist: twine; extra == "all"
|
|
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...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2019 Nicholas Tollervey, written for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
CircUp -- a utility to manage and update libraries on a CircuitPython device.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from circup.shared import DATA_DIR, BAD_FILE_FORMAT, extract_metadata, _get_modules_file
|
|
10
|
+
from circup.backends import WebBackend, DiskBackend
|
|
11
|
+
from circup.logging import logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Useful constants.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__version__ = "0.0.0-auto.0"
|
|
18
|
+
__repo__ = "https://github.com/adafruit/circup.git"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from circup.commands import *
|
|
22
|
+
|
|
23
|
+
# Allows execution via `python -m circup ...`
|
|
24
|
+
# pylint: disable=no-value-for-parameter
|
|
25
|
+
if __name__ == "__main__": # pragma: no cover
|
|
26
|
+
main()
|
|
@@ -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.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2019 Nicholas Tollervey, 2024 Tim Cocks, written for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
Class that represents a specific release of a Bundle.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
from circup.shared import (
|
|
14
|
+
DATA_DIR,
|
|
15
|
+
PLATFORMS,
|
|
16
|
+
REQUESTS_TIMEOUT,
|
|
17
|
+
tags_data_load,
|
|
18
|
+
get_latest_release_from_url,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from circup.logging import logger
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Bundle:
|
|
25
|
+
"""
|
|
26
|
+
All the links and file names for a bundle
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, repo):
|
|
30
|
+
"""
|
|
31
|
+
Initialise a Bundle created from its github info.
|
|
32
|
+
Construct all the strings in one place.
|
|
33
|
+
|
|
34
|
+
:param str repo: Repository string for github: "user/repository"
|
|
35
|
+
"""
|
|
36
|
+
vendor, bundle_id = repo.split("/")
|
|
37
|
+
bundle_id = bundle_id.lower().replace("_", "-")
|
|
38
|
+
self.key = repo
|
|
39
|
+
#
|
|
40
|
+
self.url = "https://github.com/" + repo
|
|
41
|
+
self.basename = bundle_id + "-{platform}-{tag}"
|
|
42
|
+
self.urlzip = self.basename + ".zip"
|
|
43
|
+
self.dir = os.path.join(DATA_DIR, vendor, bundle_id + "-{platform}")
|
|
44
|
+
self.zip = os.path.join(DATA_DIR, bundle_id + "-{platform}.zip")
|
|
45
|
+
self.url_format = self.url + "/releases/download/{tag}/" + self.urlzip
|
|
46
|
+
# tag
|
|
47
|
+
self._current = None
|
|
48
|
+
self._latest = None
|
|
49
|
+
|
|
50
|
+
def lib_dir(self, platform):
|
|
51
|
+
"""
|
|
52
|
+
This bundle's lib directory for the platform.
|
|
53
|
+
|
|
54
|
+
:param str platform: The platform identifier (py/6mpy/...).
|
|
55
|
+
:return: The path to the lib directory for the platform.
|
|
56
|
+
"""
|
|
57
|
+
tag = self.current_tag
|
|
58
|
+
return os.path.join(
|
|
59
|
+
self.dir.format(platform=platform),
|
|
60
|
+
self.basename.format(platform=PLATFORMS[platform], tag=tag),
|
|
61
|
+
"lib",
|
|
62
|
+
)
|
|
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
|
+
|
|
78
|
+
def requirements_for(self, library_name, toml_file=False):
|
|
79
|
+
"""
|
|
80
|
+
The requirements file for this library.
|
|
81
|
+
|
|
82
|
+
:param str library_name: The name of the library.
|
|
83
|
+
:return: The path to the requirements.txt file.
|
|
84
|
+
"""
|
|
85
|
+
platform = "py"
|
|
86
|
+
tag = self.current_tag
|
|
87
|
+
found_file = os.path.join(
|
|
88
|
+
self.dir.format(platform=platform),
|
|
89
|
+
self.basename.format(platform=PLATFORMS[platform], tag=tag),
|
|
90
|
+
"requirements",
|
|
91
|
+
library_name,
|
|
92
|
+
"requirements.txt" if not toml_file else "pyproject.toml",
|
|
93
|
+
)
|
|
94
|
+
if os.path.isfile(found_file):
|
|
95
|
+
with open(found_file, "r", encoding="utf-8") as read_this:
|
|
96
|
+
return read_this.read()
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def current_tag(self):
|
|
101
|
+
"""
|
|
102
|
+
Lazy load current cached tag from the BUNDLE_DATA json file.
|
|
103
|
+
|
|
104
|
+
:return: The current cached tag value for the project.
|
|
105
|
+
"""
|
|
106
|
+
if self._current is None:
|
|
107
|
+
self._current = tags_data_load(logger).get(self.key, "0")
|
|
108
|
+
return self._current
|
|
109
|
+
|
|
110
|
+
@current_tag.setter
|
|
111
|
+
def current_tag(self, tag):
|
|
112
|
+
"""
|
|
113
|
+
Set the current cached tag (after updating).
|
|
114
|
+
|
|
115
|
+
:param str tag: The new value for the current tag.
|
|
116
|
+
:return: The current cached tag value for the project.
|
|
117
|
+
"""
|
|
118
|
+
self._current = tag
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def latest_tag(self):
|
|
122
|
+
"""
|
|
123
|
+
Lazy find the value of the latest tag for the bundle.
|
|
124
|
+
|
|
125
|
+
:return: The most recent tag value for the project.
|
|
126
|
+
"""
|
|
127
|
+
if self._latest is None:
|
|
128
|
+
self._latest = get_latest_release_from_url(
|
|
129
|
+
self.url + "/releases/latest", logger
|
|
130
|
+
)
|
|
131
|
+
return self._latest
|
|
132
|
+
|
|
133
|
+
def validate(self):
|
|
134
|
+
"""
|
|
135
|
+
Test the existence of the expected URLs (not their content)
|
|
136
|
+
"""
|
|
137
|
+
tag = self.latest_tag
|
|
138
|
+
if not tag or tag == "releases":
|
|
139
|
+
if "--verbose" in sys.argv:
|
|
140
|
+
click.secho(f' Invalid tag "{tag}"', fg="red")
|
|
141
|
+
return False
|
|
142
|
+
for platform in PLATFORMS.values():
|
|
143
|
+
url = self.url_format.format(platform=platform, tag=tag)
|
|
144
|
+
r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
|
|
145
|
+
# pylint: disable=no-member
|
|
146
|
+
if r.status_code != requests.codes.ok:
|
|
147
|
+
if "--verbose" in sys.argv:
|
|
148
|
+
click.secho(f" Unable to find {os.path.split(url)[1]}", fg="red")
|
|
149
|
+
return False
|
|
150
|
+
# pylint: enable=no-member
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
def __repr__(self):
|
|
154
|
+
"""
|
|
155
|
+
Helps with log files.
|
|
156
|
+
|
|
157
|
+
:return: A repr of a dictionary containing the Bundles's metadata.
|
|
158
|
+
"""
|
|
159
|
+
return repr(
|
|
160
|
+
{
|
|
161
|
+
"key": self.key,
|
|
162
|
+
"url": self.url,
|
|
163
|
+
"urlzip": self.urlzip,
|
|
164
|
+
"dir": self.dir,
|
|
165
|
+
"zip": self.zip,
|
|
166
|
+
"url_format": self.url_format,
|
|
167
|
+
"current": self._current,
|
|
168
|
+
"latest": self._latest,
|
|
169
|
+
}
|
|
170
|
+
)
|