circup 2.3.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
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/backends.py +11 -14
- circup/bundle.py +98 -39
- circup/command_utils.py +106 -52
- circup/commands.py +106 -44
- circup/lazy_metadata.py +2 -1
- circup/module.py +1 -9
- circup/shared.py +5 -2
- circup/wwshell/commands.py +2 -2
- {circup-2.3.0.dist-info → circup-3.0.0.dist-info}/METADATA +2 -2
- circup-3.0.0.dist-info/RECORD +21 -0
- {circup-2.3.0.dist-info → circup-3.0.0.dist-info}/WHEEL +1 -1
- circup-2.3.0.dist-info/RECORD +0 -21
- {circup-2.3.0.dist-info → circup-3.0.0.dist-info}/entry_points.txt +0 -0
- {circup-2.3.0.dist-info → circup-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {circup-2.3.0.dist-info → circup-3.0.0.dist-info}/top_level.txt +0 -0
circup/backends.py
CHANGED
|
@@ -139,7 +139,7 @@ class Backend:
|
|
|
139
139
|
if name in device_modules:
|
|
140
140
|
if not upgrade:
|
|
141
141
|
# skip already installed modules if no -upgrade flag
|
|
142
|
-
click.echo("'{}' is already installed."
|
|
142
|
+
click.echo(f"'{name}' is already installed.")
|
|
143
143
|
return
|
|
144
144
|
|
|
145
145
|
# uninstall the module before installing
|
|
@@ -197,7 +197,9 @@ class Backend:
|
|
|
197
197
|
# Create the library directory first.
|
|
198
198
|
self.create_directory(device_path, library_path)
|
|
199
199
|
if local_path is None:
|
|
200
|
-
if
|
|
200
|
+
# Fallback to the source version (py) if the bundle doesn't have
|
|
201
|
+
# a compiled version (mpy)
|
|
202
|
+
if pyext or bundle.platform is None:
|
|
201
203
|
# Use Python source for module.
|
|
202
204
|
self.install_module_py(metadata)
|
|
203
205
|
else:
|
|
@@ -205,9 +207,9 @@ class Backend:
|
|
|
205
207
|
self.install_module_mpy(bundle, metadata)
|
|
206
208
|
else:
|
|
207
209
|
self.copy_file(metadata["path"], "lib")
|
|
208
|
-
click.echo("Installed '{}'."
|
|
210
|
+
click.echo(f"Installed '{name}'.")
|
|
209
211
|
else:
|
|
210
|
-
click.echo("Unknown module named, '{}'."
|
|
212
|
+
click.echo(f"Unknown module named, '{name}'.")
|
|
211
213
|
|
|
212
214
|
# def libraries_from_imports(self, code_py, mod_names):
|
|
213
215
|
# """
|
|
@@ -303,7 +305,7 @@ class WebBackend(Backend):
|
|
|
303
305
|
socket.getaddrinfo(host, 80, proto=socket.IPPROTO_TCP)
|
|
304
306
|
except socket.gaierror as exc:
|
|
305
307
|
raise RuntimeError(
|
|
306
|
-
"Invalid host: {}."
|
|
308
|
+
f"Invalid host: {host}." + " You should remove the 'http://'"
|
|
307
309
|
if "http://" in host or "https://" in host
|
|
308
310
|
else "Could not find or connect to specified device"
|
|
309
311
|
) from exc
|
|
@@ -648,9 +650,7 @@ class WebBackend(Backend):
|
|
|
648
650
|
if not module_name:
|
|
649
651
|
# Must be a directory based module.
|
|
650
652
|
module_name = os.path.basename(os.path.dirname(metadata["path"]))
|
|
651
|
-
|
|
652
|
-
bundle_platform = "{}mpy".format(major_version)
|
|
653
|
-
bundle_path = os.path.join(bundle.lib_dir(bundle_platform), module_name)
|
|
653
|
+
bundle_path = os.path.join(bundle.lib_dir(), module_name)
|
|
654
654
|
if os.path.isdir(bundle_path):
|
|
655
655
|
|
|
656
656
|
self.install_dir_http(bundle_path)
|
|
@@ -659,7 +659,7 @@ class WebBackend(Backend):
|
|
|
659
659
|
self.install_file_http(bundle_path)
|
|
660
660
|
|
|
661
661
|
else:
|
|
662
|
-
raise
|
|
662
|
+
raise OSError("Cannot find compiled version of module.")
|
|
663
663
|
|
|
664
664
|
# pylint: enable=too-many-locals,too-many-branches
|
|
665
665
|
def install_module_py(self, metadata, location=None):
|
|
@@ -862,7 +862,6 @@ class DiskBackend(Backend):
|
|
|
862
862
|
try:
|
|
863
863
|
with open(
|
|
864
864
|
os.path.join(self.device_location, "boot_out.txt"),
|
|
865
|
-
"r",
|
|
866
865
|
encoding="utf-8",
|
|
867
866
|
) as boot:
|
|
868
867
|
boot_out_contents = boot.read()
|
|
@@ -920,9 +919,7 @@ class DiskBackend(Backend):
|
|
|
920
919
|
# Must be a directory based module.
|
|
921
920
|
module_name = os.path.basename(os.path.dirname(metadata["path"]))
|
|
922
921
|
|
|
923
|
-
|
|
924
|
-
bundle_platform = "{}mpy".format(major_version)
|
|
925
|
-
bundle_path = os.path.join(bundle.lib_dir(bundle_platform), module_name)
|
|
922
|
+
bundle_path = os.path.join(bundle.lib_dir(), module_name)
|
|
926
923
|
if os.path.isdir(bundle_path):
|
|
927
924
|
target_path = os.path.join(self.library_path, module_name)
|
|
928
925
|
# Copy the directory.
|
|
@@ -936,7 +933,7 @@ class DiskBackend(Backend):
|
|
|
936
933
|
# Copy file.
|
|
937
934
|
shutil.copyfile(bundle_path, target_path)
|
|
938
935
|
else:
|
|
939
|
-
raise
|
|
936
|
+
raise OSError("Cannot find compiled version of module.")
|
|
940
937
|
|
|
941
938
|
# pylint: enable=too-many-locals,too-many-branches
|
|
942
939
|
def install_module_py(self, metadata, location=None):
|
circup/bundle.py
CHANGED
|
@@ -10,6 +10,8 @@ import sys
|
|
|
10
10
|
import click
|
|
11
11
|
import requests
|
|
12
12
|
|
|
13
|
+
from semver import VersionInfo
|
|
14
|
+
|
|
13
15
|
from circup.shared import (
|
|
14
16
|
DATA_DIR,
|
|
15
17
|
PLATFORMS,
|
|
@@ -20,11 +22,14 @@ from circup.shared import (
|
|
|
20
22
|
from circup.logging import logger
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
class Bundle:
|
|
25
|
+
class Bundle: # pylint: disable=too-many-instance-attributes
|
|
24
26
|
"""
|
|
25
27
|
All the links and file names for a bundle
|
|
26
28
|
"""
|
|
27
29
|
|
|
30
|
+
#: Avoid requests to the internet
|
|
31
|
+
offline = False
|
|
32
|
+
|
|
28
33
|
def __init__(self, repo):
|
|
29
34
|
"""
|
|
30
35
|
Initialise a Bundle created from its github info.
|
|
@@ -47,29 +52,40 @@ class Bundle:
|
|
|
47
52
|
self._latest = None
|
|
48
53
|
self.pinned_tag = None
|
|
49
54
|
self._available = []
|
|
55
|
+
#
|
|
56
|
+
self.platform = None
|
|
50
57
|
|
|
51
|
-
def lib_dir(self,
|
|
58
|
+
def lib_dir(self, source=False):
|
|
52
59
|
"""
|
|
53
|
-
This bundle's lib directory for the
|
|
60
|
+
This bundle's lib directory for the bundle's source or compiled version.
|
|
54
61
|
|
|
55
|
-
:param
|
|
56
|
-
|
|
62
|
+
:param bool source: Whether to return the path to the source lib
|
|
63
|
+
directory or to :py:attr:`self.platform`'s lib directory. If `source` is
|
|
64
|
+
`False` but :py:attr:`self.platform` is None, the source lib directory
|
|
65
|
+
will be returned instead.
|
|
66
|
+
:return: The path to the lib directory.
|
|
57
67
|
"""
|
|
58
68
|
tag = self.current_tag
|
|
69
|
+
platform = "py" if source or not self.platform else self.platform
|
|
59
70
|
return os.path.join(
|
|
60
71
|
self.dir.format(platform=platform),
|
|
61
72
|
self.basename.format(platform=PLATFORMS[platform], tag=tag),
|
|
62
73
|
"lib",
|
|
63
74
|
)
|
|
64
75
|
|
|
65
|
-
def examples_dir(self,
|
|
76
|
+
def examples_dir(self, source=False):
|
|
66
77
|
"""
|
|
67
|
-
This bundle's examples directory for the
|
|
78
|
+
This bundle's examples directory for the bundle's source or compiled
|
|
79
|
+
version.
|
|
68
80
|
|
|
69
|
-
:param
|
|
70
|
-
|
|
81
|
+
:param bool source: Whether to return the path to the source examples
|
|
82
|
+
directory or to :py:attr:`self.platform`'s examples directory. If
|
|
83
|
+
`source` is `False` but :py:attr:`self.platform` is None, the source
|
|
84
|
+
examples directory will be returned instead.
|
|
85
|
+
:return: The path to the examples directory.
|
|
71
86
|
"""
|
|
72
87
|
tag = self.current_tag
|
|
88
|
+
platform = "py" if source or not self.platform else self.platform
|
|
73
89
|
return os.path.join(
|
|
74
90
|
self.dir.format(platform=platform),
|
|
75
91
|
self.basename.format(platform=PLATFORMS[platform], tag=tag),
|
|
@@ -93,7 +109,7 @@ class Bundle:
|
|
|
93
109
|
"requirements.txt" if not toml_file else "pyproject.toml",
|
|
94
110
|
)
|
|
95
111
|
if os.path.isfile(found_file):
|
|
96
|
-
with open(found_file,
|
|
112
|
+
with open(found_file, encoding="utf-8") as read_this:
|
|
97
113
|
return read_this.read()
|
|
98
114
|
return None
|
|
99
115
|
|
|
@@ -101,18 +117,25 @@ class Bundle:
|
|
|
101
117
|
def current_tag(self):
|
|
102
118
|
"""
|
|
103
119
|
The current tag for the project. If the tag hasn't been explicitly set
|
|
104
|
-
this will be the pinned tag, if one is set
|
|
105
|
-
this will be the latest available tag that is locally
|
|
120
|
+
this will be the pinned tag, if one is set and it is available. If there
|
|
121
|
+
is no pinned tag, this will be the latest available tag that is locally
|
|
122
|
+
available.
|
|
106
123
|
|
|
107
124
|
:return: The current tag value for the project.
|
|
108
125
|
"""
|
|
109
126
|
if self._current is None:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
if self.pinned_tag:
|
|
128
|
+
self._current = (
|
|
129
|
+
self.pinned_tag if self.pinned_tag in self._available else None
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
self._current = (
|
|
133
|
+
# This represents the latest version locally available
|
|
134
|
+
self._available[-1]
|
|
135
|
+
if len(self._available) > 0
|
|
136
|
+
else None
|
|
137
|
+
)
|
|
138
|
+
|
|
116
139
|
return self._current
|
|
117
140
|
|
|
118
141
|
@current_tag.setter
|
|
@@ -132,9 +155,12 @@ class Bundle:
|
|
|
132
155
|
:return: The most recent tag value for the project.
|
|
133
156
|
"""
|
|
134
157
|
if self._latest is None:
|
|
135
|
-
self.
|
|
136
|
-
self.
|
|
137
|
-
|
|
158
|
+
if self.offline:
|
|
159
|
+
self._latest = self._available[-1] if len(self._available) > 0 else None
|
|
160
|
+
else:
|
|
161
|
+
self._latest = get_latest_release_from_url(
|
|
162
|
+
self.url + "/releases/latest", logger
|
|
163
|
+
)
|
|
138
164
|
return self._latest
|
|
139
165
|
|
|
140
166
|
@property
|
|
@@ -155,7 +181,15 @@ class Bundle:
|
|
|
155
181
|
"""
|
|
156
182
|
if isinstance(tags, str):
|
|
157
183
|
tags = [tags]
|
|
158
|
-
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
tags = sorted(tags, key=self.parse_version)
|
|
187
|
+
except ValueError as ex:
|
|
188
|
+
logger.warning(
|
|
189
|
+
"Bundle '%s' has invalid tags, cannot order by version.", self.key
|
|
190
|
+
)
|
|
191
|
+
logger.warning(ex)
|
|
192
|
+
self._available = tags
|
|
159
193
|
|
|
160
194
|
def add_tag(self, tag: str) -> None:
|
|
161
195
|
"""
|
|
@@ -171,34 +205,59 @@ class Bundle:
|
|
|
171
205
|
# The tag is already stored for some reason, lets not add it again
|
|
172
206
|
return
|
|
173
207
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
208
|
+
try:
|
|
209
|
+
version_tag = self.parse_version(tag)
|
|
210
|
+
|
|
211
|
+
for rev_i, available_tag in enumerate(reversed(self._available)):
|
|
212
|
+
available_version_tag = self.parse_version(available_tag)
|
|
213
|
+
if version_tag > available_version_tag:
|
|
214
|
+
i = len(self._available) - rev_i
|
|
215
|
+
self._available.insert(i, tag)
|
|
216
|
+
break
|
|
217
|
+
else:
|
|
218
|
+
self._available.insert(0, tag)
|
|
219
|
+
except ValueError as ex:
|
|
220
|
+
logger.warning(
|
|
221
|
+
"Bundle tag '%s' is not a valid tag, cannot order by version.", tag
|
|
222
|
+
)
|
|
223
|
+
logger.warning(ex)
|
|
224
|
+
self._available.append(tag)
|
|
181
225
|
|
|
182
226
|
def validate(self):
|
|
183
227
|
"""
|
|
184
|
-
Test the existence of the expected
|
|
228
|
+
Test the existence of the expected URL (not the content)
|
|
185
229
|
"""
|
|
186
230
|
tag = self.latest_tag
|
|
187
231
|
if not tag or tag == "releases":
|
|
188
232
|
if "--verbose" in sys.argv:
|
|
189
233
|
click.secho(f' Invalid tag "{tag}"', fg="red")
|
|
190
234
|
return False
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
# pylint: enable=no-member
|
|
235
|
+
url = self.url_format.format(platform="py", tag=tag)
|
|
236
|
+
r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
|
|
237
|
+
# pylint: disable=no-member
|
|
238
|
+
if r.status_code != requests.codes.ok:
|
|
239
|
+
if "--verbose" in sys.argv:
|
|
240
|
+
click.secho(f" Unable to find {os.path.split(url)[1]}", fg="red")
|
|
241
|
+
return False
|
|
242
|
+
# pylint: enable=no-member
|
|
200
243
|
return True
|
|
201
244
|
|
|
245
|
+
@staticmethod
|
|
246
|
+
def parse_version(tag: str) -> VersionInfo:
|
|
247
|
+
"""
|
|
248
|
+
Parse a tag to get a VersionInfo object.
|
|
249
|
+
|
|
250
|
+
`VersionInfo` objects are useful for ordering the tags from oldest to
|
|
251
|
+
newest in :py:attr:`self.available_tags`. The tags are stripped of a
|
|
252
|
+
leading 'v' (if one is present) and minor and patch components are
|
|
253
|
+
optional. This is to allow more flexibility with how a bundle is
|
|
254
|
+
versioned.
|
|
255
|
+
|
|
256
|
+
:param str tag: The tag to parse.
|
|
257
|
+
:return: A `VersionInfo` object parsed from the tag.
|
|
258
|
+
"""
|
|
259
|
+
return VersionInfo.parse(tag.removeprefix("v"), optional_minor_and_patch=True)
|
|
260
|
+
|
|
202
261
|
def __repr__(self):
|
|
203
262
|
"""
|
|
204
263
|
Helps with log files.
|
circup/command_utils.py
CHANGED
|
@@ -23,6 +23,7 @@ import click
|
|
|
23
23
|
from circup.shared import (
|
|
24
24
|
PLATFORMS,
|
|
25
25
|
REQUESTS_TIMEOUT,
|
|
26
|
+
SUPPORTED_PLATFORMS,
|
|
26
27
|
_get_modules_file,
|
|
27
28
|
BUNDLE_CONFIG_OVERWRITE,
|
|
28
29
|
BUNDLE_CONFIG_FILE,
|
|
@@ -145,29 +146,71 @@ def ensure_bundle_tag(bundle, tag):
|
|
|
145
146
|
|
|
146
147
|
:return: If the bundle is available.
|
|
147
148
|
"""
|
|
148
|
-
|
|
149
|
+
if tag is None:
|
|
150
|
+
logger.warning("Bundle version requested is 'None'.")
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
do_update_source = False
|
|
154
|
+
do_update_compiled = False
|
|
149
155
|
if tag in bundle.available_tags:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
# missing directories (new platform added on an existing install
|
|
157
|
+
# or side effect of pytest or network errors)
|
|
158
|
+
# Check for the source
|
|
159
|
+
do_update_source = not os.path.isdir(bundle.lib_dir(source=True))
|
|
160
|
+
do_update_compiled = bundle.platform is not None and not os.path.isdir(
|
|
161
|
+
bundle.lib_dir(source=False)
|
|
162
|
+
)
|
|
154
163
|
else:
|
|
155
|
-
|
|
164
|
+
do_update_source = True
|
|
165
|
+
do_update_compiled = bundle.platform is not None
|
|
166
|
+
|
|
167
|
+
if not (do_update_source or do_update_compiled):
|
|
168
|
+
logger.info("Current bundle version available (%s).", tag)
|
|
169
|
+
return True
|
|
156
170
|
|
|
157
|
-
if
|
|
158
|
-
|
|
171
|
+
if Bundle.offline:
|
|
172
|
+
if do_update_source: # pylint: disable=no-else-return
|
|
173
|
+
logger.info(
|
|
174
|
+
"Bundle version not available but skipping update in offline mode."
|
|
175
|
+
)
|
|
176
|
+
return False
|
|
177
|
+
else:
|
|
178
|
+
logger.info(
|
|
179
|
+
"Bundle platform not available. Falling back to source (.py) files in offline mode."
|
|
180
|
+
)
|
|
181
|
+
bundle.platform = None
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
logger.info("New version available (%s).", tag)
|
|
185
|
+
if do_update_source:
|
|
159
186
|
try:
|
|
160
|
-
get_bundle(bundle, tag)
|
|
161
|
-
tags_data_save_tags(bundle.key, bundle.available_tags)
|
|
187
|
+
get_bundle(bundle, tag, "py")
|
|
162
188
|
except requests.exceptions.HTTPError as ex:
|
|
163
189
|
click.secho(
|
|
164
|
-
f"There was a problem downloading the {bundle.key} bundle.",
|
|
190
|
+
f"There was a problem downloading the 'py' platform for the '{bundle.key}' bundle.",
|
|
191
|
+
fg="red",
|
|
165
192
|
)
|
|
166
193
|
logger.exception(ex)
|
|
167
|
-
return False
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
return False # Bundle isn't available
|
|
195
|
+
bundle.add_tag(tag)
|
|
196
|
+
tags_data_save_tags(bundle.key, bundle.available_tags)
|
|
197
|
+
|
|
198
|
+
if do_update_compiled:
|
|
199
|
+
try:
|
|
200
|
+
get_bundle(bundle, tag, bundle.platform)
|
|
201
|
+
except requests.exceptions.HTTPError as ex:
|
|
202
|
+
click.secho(
|
|
203
|
+
(
|
|
204
|
+
f"There was a problem downloading the '{bundle.platform}' platform for the "
|
|
205
|
+
f"'{bundle.key}' bundle.\nFalling back to source (.py) files."
|
|
206
|
+
),
|
|
207
|
+
fg="red",
|
|
208
|
+
)
|
|
209
|
+
logger.exception(ex)
|
|
210
|
+
bundle.platform = None # Compiled isn't available, source is good
|
|
211
|
+
bundle.current_tag = tag
|
|
212
|
+
|
|
213
|
+
return True # bundle is available
|
|
171
214
|
|
|
172
215
|
|
|
173
216
|
def ensure_latest_bundle(bundle):
|
|
@@ -286,7 +329,7 @@ def find_device():
|
|
|
286
329
|
old_mode = ctypes.windll.kernel32.SetErrorMode(1)
|
|
287
330
|
try:
|
|
288
331
|
for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
|
|
289
|
-
path = "{}:\\"
|
|
332
|
+
path = f"{disk}:\\"
|
|
290
333
|
if os.path.exists(path) and get_volume_name(path) == "CIRCUITPY":
|
|
291
334
|
device_dir = path
|
|
292
335
|
# Report only the FIRST device found.
|
|
@@ -295,7 +338,7 @@ def find_device():
|
|
|
295
338
|
ctypes.windll.kernel32.SetErrorMode(old_mode)
|
|
296
339
|
else:
|
|
297
340
|
# No support for unknown operating systems.
|
|
298
|
-
raise NotImplementedError('OS "{}" not supported.'
|
|
341
|
+
raise NotImplementedError(f'OS "{os.name}" not supported.')
|
|
299
342
|
logger.info("Found device: %s", device_dir)
|
|
300
343
|
return device_dir
|
|
301
344
|
|
|
@@ -349,45 +392,44 @@ def find_modules(backend, bundles_list):
|
|
|
349
392
|
# If it's not possible to get the device and bundle metadata, bail out
|
|
350
393
|
# with a friendly message and indication of what's gone wrong.
|
|
351
394
|
logger.exception(ex)
|
|
352
|
-
click.echo("There was a problem: {}"
|
|
395
|
+
click.echo(f"There was a problem: {ex}")
|
|
353
396
|
sys.exit(1)
|
|
354
397
|
# pylint: enable=broad-except,too-many-locals
|
|
355
398
|
|
|
356
399
|
|
|
357
|
-
def get_bundle(bundle, tag):
|
|
400
|
+
def get_bundle(bundle, tag, platform):
|
|
358
401
|
"""
|
|
359
402
|
Downloads and extracts the version of the bundle with the referenced tag.
|
|
360
403
|
The resulting zip file is saved on the local filesystem.
|
|
361
404
|
|
|
362
405
|
:param Bundle bundle: the target Bundle object.
|
|
363
406
|
:param str tag: The GIT tag to use to download the bundle.
|
|
407
|
+
:param str platform: The platform string (i.e. '10mpy').
|
|
364
408
|
"""
|
|
365
|
-
click.echo(f"Downloading
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
bundle.add_tag(tag)
|
|
390
|
-
bundle.current_tag = tag
|
|
409
|
+
click.echo(f"Downloading '{platform}' bundle for {bundle.key} ({tag}).")
|
|
410
|
+
github_string = PLATFORMS[platform]
|
|
411
|
+
# Report the platform: "8.x-mpy", etc.
|
|
412
|
+
click.echo(f"{github_string}:")
|
|
413
|
+
url = bundle.url_format.format(platform=github_string, tag=tag)
|
|
414
|
+
logger.info("Downloading bundle: %s", url)
|
|
415
|
+
r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
|
|
416
|
+
# pylint: disable=no-member
|
|
417
|
+
if r.status_code != requests.codes.ok:
|
|
418
|
+
logger.warning("Unable to connect to %s", url)
|
|
419
|
+
r.raise_for_status()
|
|
420
|
+
# pylint: enable=no-member
|
|
421
|
+
total_size = int(r.headers.get("Content-Length"))
|
|
422
|
+
temp_zip = bundle.zip.format(platform=platform)
|
|
423
|
+
with click.progressbar(
|
|
424
|
+
r.iter_content(1024), label="Extracting:", length=total_size
|
|
425
|
+
) as pbar, open(temp_zip, "wb") as zip_fp:
|
|
426
|
+
for chunk in pbar:
|
|
427
|
+
zip_fp.write(chunk)
|
|
428
|
+
pbar.update(len(chunk))
|
|
429
|
+
logger.info("Saved to %s", temp_zip)
|
|
430
|
+
temp_dir = bundle.dir.format(platform=platform)
|
|
431
|
+
with zipfile.ZipFile(temp_zip, "r") as zfile:
|
|
432
|
+
zfile.extractall(temp_dir)
|
|
391
433
|
click.echo("\nOK\n")
|
|
392
434
|
|
|
393
435
|
|
|
@@ -407,12 +449,12 @@ def get_bundle_examples(bundles_list, avoid_download=False):
|
|
|
407
449
|
|
|
408
450
|
try:
|
|
409
451
|
for bundle in bundles_list:
|
|
410
|
-
if not avoid_download or not os.path.isdir(bundle.lib_dir(
|
|
452
|
+
if not avoid_download or not os.path.isdir(bundle.lib_dir(source=True)):
|
|
411
453
|
ensure_bundle(bundle)
|
|
412
|
-
path = bundle.examples_dir(
|
|
454
|
+
path = bundle.examples_dir(source=True)
|
|
413
455
|
meta_saved = os.path.join(path, "../bundle_examples.json")
|
|
414
456
|
if os.path.exists(meta_saved):
|
|
415
|
-
with open(meta_saved,
|
|
457
|
+
with open(meta_saved, encoding="utf-8") as f:
|
|
416
458
|
bundle_examples = json.load(f)
|
|
417
459
|
all_the_examples.update(bundle_examples)
|
|
418
460
|
bundle_examples.clear()
|
|
@@ -455,9 +497,9 @@ def get_bundle_versions(bundles_list, avoid_download=False):
|
|
|
455
497
|
"""
|
|
456
498
|
all_the_modules = dict()
|
|
457
499
|
for bundle in bundles_list:
|
|
458
|
-
if not avoid_download or not os.path.isdir(bundle.lib_dir(
|
|
500
|
+
if not avoid_download or not os.path.isdir(bundle.lib_dir(source=True)):
|
|
459
501
|
ensure_bundle(bundle)
|
|
460
|
-
path = bundle.lib_dir(
|
|
502
|
+
path = bundle.lib_dir(source=True)
|
|
461
503
|
path_modules = _get_modules_file(path, logger)
|
|
462
504
|
for name, module in path_modules.items():
|
|
463
505
|
module["bundle"] = bundle
|
|
@@ -504,12 +546,14 @@ def get_bundles_local_dict():
|
|
|
504
546
|
return dict()
|
|
505
547
|
|
|
506
548
|
|
|
507
|
-
def get_bundles_list(bundle_tags):
|
|
549
|
+
def get_bundles_list(bundle_tags, platform_version=None):
|
|
508
550
|
"""
|
|
509
551
|
Retrieve the list of bundles from the config dictionary.
|
|
510
552
|
|
|
511
553
|
:param Dict[str,str]|None bundle_tags: Pinned bundle tags. These override
|
|
512
554
|
any tags found in the pyproject.toml.
|
|
555
|
+
:param str platform_version: The platform version needed for the current
|
|
556
|
+
device.
|
|
513
557
|
:return: List of supported bundles as Bundle objects.
|
|
514
558
|
"""
|
|
515
559
|
bundle_config = get_bundles_dict()
|
|
@@ -524,6 +568,7 @@ def get_bundles_list(bundle_tags):
|
|
|
524
568
|
|
|
525
569
|
bundles_list = [Bundle(bundle_config[b]) for b in bundle_config]
|
|
526
570
|
for bundle in bundles_list:
|
|
571
|
+
bundle.platform = platform_version
|
|
527
572
|
bundle.available_tags = tags.get(bundle.key, [])
|
|
528
573
|
if pinned_tags is not None:
|
|
529
574
|
bundle.pinned_tag = pinned_tags.get(bundle.key)
|
|
@@ -884,7 +929,7 @@ def libraries_from_auto_file(backend, auto_file, mod_names):
|
|
|
884
929
|
# pass a local file with "./" or "../"
|
|
885
930
|
is_relative = auto_file.split(os.sep)[0] in [os.path.curdir, os.path.pardir]
|
|
886
931
|
if os.path.isabs(auto_file) or is_relative:
|
|
887
|
-
with open(auto_file,
|
|
932
|
+
with open(auto_file, encoding="UTF8") as fp:
|
|
888
933
|
auto_file_content = fp.read()
|
|
889
934
|
else:
|
|
890
935
|
auto_file_content = backend.get_file_content(auto_file)
|
|
@@ -994,3 +1039,12 @@ def parse_cli_bundle_tags(bundle_tags_cli):
|
|
|
994
1039
|
if len(item) == 2:
|
|
995
1040
|
bundle_tags[item[0].strip()] = item[1].strip()
|
|
996
1041
|
return bundle_tags if len(bundle_tags) > 0 else None
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def pretty_supported_cpy_versions():
|
|
1045
|
+
"""Return a user friendly string of the supported CircuitPython versions."""
|
|
1046
|
+
supported_cpy = [
|
|
1047
|
+
PLATFORMS[platform].split("-", maxsplit=1)[0]
|
|
1048
|
+
for platform in SUPPORTED_PLATFORMS
|
|
1049
|
+
]
|
|
1050
|
+
return ", ".join(supported_cpy)
|
circup/commands.py
CHANGED
|
@@ -24,7 +24,11 @@ import requests
|
|
|
24
24
|
|
|
25
25
|
from circup.backends import WebBackend, DiskBackend
|
|
26
26
|
from circup.logging import logger, log_formatter, LOGFILE
|
|
27
|
-
from circup.shared import
|
|
27
|
+
from circup.shared import (
|
|
28
|
+
BOARDLESS_COMMANDS,
|
|
29
|
+
SUPPORTED_PLATFORMS,
|
|
30
|
+
get_latest_release_from_url,
|
|
31
|
+
)
|
|
28
32
|
from circup.bundle import Bundle
|
|
29
33
|
from circup.command_utils import (
|
|
30
34
|
get_device_path,
|
|
@@ -38,6 +42,7 @@ from circup.command_utils import (
|
|
|
38
42
|
get_dependencies,
|
|
39
43
|
get_bundles_local_dict,
|
|
40
44
|
parse_cli_bundle_tags,
|
|
45
|
+
pretty_supported_cpy_versions,
|
|
41
46
|
save_local_bundles,
|
|
42
47
|
get_bundles_dict,
|
|
43
48
|
completion_for_example,
|
|
@@ -68,6 +73,16 @@ from circup.command_utils import (
|
|
|
68
73
|
" You can optionally set an environment variable CIRCUP_WEBWORKFLOW_PASSWORD"
|
|
69
74
|
" instead of passing this argument. If both exist the CLI arg takes precedent.",
|
|
70
75
|
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--offline",
|
|
78
|
+
is_flag=True,
|
|
79
|
+
help="Prevents Circup from accessing the internet for any reason. "
|
|
80
|
+
"Without this flag, Circup will fail with an error if it needs to access "
|
|
81
|
+
"the network and the network is not available. With this flag, Circup "
|
|
82
|
+
"will attempt to proceed without the network if possible. Circup will "
|
|
83
|
+
"only use bundles downloaded locally even if there might be newer "
|
|
84
|
+
"versions available.",
|
|
85
|
+
)
|
|
71
86
|
@click.option(
|
|
72
87
|
"--timeout",
|
|
73
88
|
default=30,
|
|
@@ -95,6 +110,13 @@ from circup.command_utils import (
|
|
|
95
110
|
"version values provided here will override any pinned values from the "
|
|
96
111
|
"pyproject.toml.",
|
|
97
112
|
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"--allow-unsupported",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="Allow using a device with a version of CircuitPython that is no longer "
|
|
117
|
+
"supported. Using an unsupported version of CircuitPython is generally not "
|
|
118
|
+
"recommended because libraries may not work with it.",
|
|
119
|
+
)
|
|
98
120
|
@click.version_option(
|
|
99
121
|
prog_name="Circup",
|
|
100
122
|
message="%(prog)s, A CircuitPython module updater. Version %(version)s",
|
|
@@ -107,10 +129,12 @@ def main( # pylint: disable=too-many-locals
|
|
|
107
129
|
host,
|
|
108
130
|
port,
|
|
109
131
|
password,
|
|
132
|
+
offline,
|
|
110
133
|
timeout,
|
|
111
134
|
board_id,
|
|
112
135
|
cpy_version,
|
|
113
136
|
bundle_versions,
|
|
137
|
+
allow_unsupported,
|
|
114
138
|
): # pragma: no cover
|
|
115
139
|
"""
|
|
116
140
|
A tool to manage and update libraries on a CircuitPython device.
|
|
@@ -121,6 +145,7 @@ def main( # pylint: disable=too-many-locals
|
|
|
121
145
|
ctx.obj["BUNDLE_TAGS"] = (
|
|
122
146
|
parse_cli_bundle_tags(bundle_versions) if len(bundle_versions) > 0 else None
|
|
123
147
|
)
|
|
148
|
+
Bundle.offline = offline
|
|
124
149
|
|
|
125
150
|
if password is None:
|
|
126
151
|
password = os.getenv("CIRCUP_WEBWORKFLOW_PASSWORD")
|
|
@@ -171,26 +196,28 @@ def main( # pylint: disable=too-many-locals
|
|
|
171
196
|
verbose_handler.setLevel(logging.INFO)
|
|
172
197
|
verbose_handler.setFormatter(log_formatter)
|
|
173
198
|
logger.addHandler(verbose_handler)
|
|
174
|
-
click.echo("Logging to {}\n"
|
|
199
|
+
click.echo(f"Logging to {LOGFILE}\n")
|
|
175
200
|
else:
|
|
176
201
|
ctx.obj["verbose"] = False
|
|
177
202
|
|
|
178
203
|
logger.info("### Started Circup ###")
|
|
179
204
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
if offline:
|
|
206
|
+
logger.info(
|
|
207
|
+
"'--offline' flag present, all update checks requiring the network will be skipped."
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
# If a newer version of circup is available, print a message.
|
|
211
|
+
logger.info("Checking for a newer version of circup")
|
|
212
|
+
version = get_circup_version()
|
|
213
|
+
if version:
|
|
214
|
+
update_checker.update_check("circup", version)
|
|
185
215
|
|
|
186
216
|
# stop early if the command is boardless
|
|
187
217
|
if ctx.invoked_subcommand in BOARDLESS_COMMANDS or "--help" in sys.argv:
|
|
188
218
|
return
|
|
189
219
|
|
|
190
220
|
ctx.obj["DEVICE_PATH"] = device_path
|
|
191
|
-
latest_version = get_latest_release_from_url(
|
|
192
|
-
"https://github.com/adafruit/circuitpython/releases/latest", logger
|
|
193
|
-
)
|
|
194
221
|
|
|
195
222
|
if device_path is None or not ctx.obj["backend"].is_device_present():
|
|
196
223
|
click.secho("Could not find a connected CircuitPython device.", fg="red")
|
|
@@ -201,27 +228,54 @@ def main( # pylint: disable=too-many-locals
|
|
|
201
228
|
if board_id is None or cpy_version is None
|
|
202
229
|
else (cpy_version, board_id)
|
|
203
230
|
)
|
|
231
|
+
major_version = cpy_version.split(".")[0]
|
|
232
|
+
bundle_platform = "{}mpy".format(major_version)
|
|
233
|
+
ctx.obj["DEVICE_PLATFORM_VERSION"] = bundle_platform
|
|
204
234
|
click.echo(
|
|
205
235
|
"Found device {} at {}, running CircuitPython {}.".format(
|
|
206
236
|
board_id, device_path, cpy_version
|
|
207
237
|
)
|
|
208
238
|
)
|
|
209
|
-
|
|
210
|
-
|
|
239
|
+
|
|
240
|
+
if not offline:
|
|
241
|
+
latest_version = get_latest_release_from_url(
|
|
242
|
+
"https://github.com/adafruit/circuitpython/releases/latest", logger
|
|
243
|
+
)
|
|
244
|
+
try:
|
|
245
|
+
if VersionInfo.parse(cpy_version) < VersionInfo.parse(latest_version):
|
|
246
|
+
click.secho(
|
|
247
|
+
"A newer version of CircuitPython ({}) is available.".format(
|
|
248
|
+
latest_version
|
|
249
|
+
),
|
|
250
|
+
fg="green",
|
|
251
|
+
)
|
|
252
|
+
if board_id:
|
|
253
|
+
url_download = f"https://circuitpython.org/board/{board_id}"
|
|
254
|
+
else:
|
|
255
|
+
url_download = "https://circuitpython.org/downloads"
|
|
256
|
+
click.secho(f"Get it here: {url_download}", fg="green")
|
|
257
|
+
except ValueError as ex:
|
|
258
|
+
logger.warning("CircuitPython has incorrect semver value.")
|
|
259
|
+
logger.warning(ex)
|
|
260
|
+
|
|
261
|
+
if not bundle_platform in SUPPORTED_PLATFORMS:
|
|
262
|
+
click.secho(
|
|
263
|
+
"The version of CircuitPython on the device is no longer supported.",
|
|
264
|
+
fg="yellow" if allow_unsupported else "red",
|
|
265
|
+
)
|
|
266
|
+
if allow_unsupported:
|
|
211
267
|
click.secho(
|
|
212
|
-
"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
fg="green",
|
|
268
|
+
"It is recommended to update to a supported version "
|
|
269
|
+
f"({pretty_supported_cpy_versions()}) to ensure compatability.",
|
|
270
|
+
fg="yellow",
|
|
216
271
|
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
logger.warning(ex)
|
|
272
|
+
else:
|
|
273
|
+
click.echo(
|
|
274
|
+
f"If you would like to continue to use version {cpy_version} of CircuitPython, "
|
|
275
|
+
"pass the '--allow-unsupported' flag with this command. Otherwise, update to a "
|
|
276
|
+
f"supported version ({pretty_supported_cpy_versions()}) to ensure compatability.",
|
|
277
|
+
)
|
|
278
|
+
sys.exit(1)
|
|
225
279
|
|
|
226
280
|
|
|
227
281
|
@main.command()
|
|
@@ -237,7 +291,7 @@ def freeze(ctx, requirement): # pragma: no cover
|
|
|
237
291
|
if modules:
|
|
238
292
|
output = []
|
|
239
293
|
for module in modules:
|
|
240
|
-
output.append("{}=={
|
|
294
|
+
output.append(f"{module.name}=={module.device_version}")
|
|
241
295
|
for module in output:
|
|
242
296
|
click.echo(module)
|
|
243
297
|
logger.info(module)
|
|
@@ -282,7 +336,10 @@ def list_cli(ctx): # pragma: no cover
|
|
|
282
336
|
modules = [
|
|
283
337
|
m.row
|
|
284
338
|
for m in find_modules(
|
|
285
|
-
ctx.obj["backend"],
|
|
339
|
+
ctx.obj["backend"],
|
|
340
|
+
get_bundles_list(
|
|
341
|
+
ctx.obj["BUNDLE_TAGS"], ctx.obj["DEVICE_PLATFORM_VERSION"]
|
|
342
|
+
),
|
|
286
343
|
)
|
|
287
344
|
if m.outofdate
|
|
288
345
|
]
|
|
@@ -293,7 +350,7 @@ def list_cli(ctx): # pragma: no cover
|
|
|
293
350
|
for row in data:
|
|
294
351
|
for i, word in enumerate(row):
|
|
295
352
|
col_width[i] = max(len(word) + 2, col_width[i])
|
|
296
|
-
dashes = tuple(
|
|
353
|
+
dashes = tuple("-" * (width - 1) for width in col_width)
|
|
297
354
|
data.insert(1, dashes)
|
|
298
355
|
click.echo(
|
|
299
356
|
"The following modules are out of date or probably need an update.\n"
|
|
@@ -359,12 +416,15 @@ def install(
|
|
|
359
416
|
|
|
360
417
|
# pylint: disable=too-many-branches
|
|
361
418
|
# TODO: Ensure there's enough space on the device
|
|
362
|
-
|
|
419
|
+
platform_version = ctx.obj["DEVICE_PLATFORM_VERSION"] if not pyext else None
|
|
420
|
+
available_modules = get_bundle_versions(
|
|
421
|
+
get_bundles_list(ctx.obj["BUNDLE_TAGS"], platform_version)
|
|
422
|
+
)
|
|
363
423
|
mod_names = {}
|
|
364
424
|
for module, metadata in available_modules.items():
|
|
365
425
|
mod_names[module.replace(".py", "").lower()] = metadata
|
|
366
426
|
if requirement:
|
|
367
|
-
with open(requirement,
|
|
427
|
+
with open(requirement, encoding="utf-8") as rfile:
|
|
368
428
|
requirements_txt = rfile.read()
|
|
369
429
|
requested_installs = libraries_from_requirements(requirements_txt)
|
|
370
430
|
elif auto or auto_file:
|
|
@@ -392,7 +452,7 @@ def install(
|
|
|
392
452
|
upgrade,
|
|
393
453
|
)
|
|
394
454
|
|
|
395
|
-
if stubs:
|
|
455
|
+
if stubs and not Bundle.offline:
|
|
396
456
|
# Check we are in a virtual environment
|
|
397
457
|
if not is_virtual_env_active():
|
|
398
458
|
if is_global_install_ok is None:
|
|
@@ -525,9 +585,7 @@ def show(ctx, match): # pragma: no cover
|
|
|
525
585
|
module_names = [m for m in module_names if match in m]
|
|
526
586
|
click.echo("\n".join(module_names))
|
|
527
587
|
|
|
528
|
-
click.echo(
|
|
529
|
-
"{} shown of {} packages.".format(len(module_names), len(available_modules))
|
|
530
|
-
)
|
|
588
|
+
click.echo(f"{len(module_names)} shown of {len(available_modules)} packages.")
|
|
531
589
|
|
|
532
590
|
|
|
533
591
|
@main.command()
|
|
@@ -551,9 +609,9 @@ def uninstall(ctx, module): # pragma: no cover
|
|
|
551
609
|
metadata = mod_names[name]
|
|
552
610
|
module_path = metadata["path"]
|
|
553
611
|
ctx.obj["backend"].uninstall(device_path, module_path)
|
|
554
|
-
click.echo("Uninstalled '{}'."
|
|
612
|
+
click.echo(f"Uninstalled '{name}'.")
|
|
555
613
|
else:
|
|
556
|
-
click.echo("Module '{}' not found on device."
|
|
614
|
+
click.echo(f"Module '{name}' not found on device.")
|
|
557
615
|
continue
|
|
558
616
|
|
|
559
617
|
|
|
@@ -581,7 +639,9 @@ def update(ctx, update_all): # pragma: no cover
|
|
|
581
639
|
"""
|
|
582
640
|
logger.info("Update")
|
|
583
641
|
# Grab current modules.
|
|
584
|
-
bundles_list = get_bundles_list(
|
|
642
|
+
bundles_list = get_bundles_list(
|
|
643
|
+
ctx.obj["BUNDLE_TAGS"], ctx.obj["DEVICE_PLATFORM_VERSION"]
|
|
644
|
+
)
|
|
585
645
|
installed_modules = find_modules(ctx.obj["backend"], bundles_list)
|
|
586
646
|
modules_to_update = [m for m in installed_modules if m.outofdate]
|
|
587
647
|
|
|
@@ -591,7 +651,7 @@ def update(ctx, update_all): # pragma: no cover
|
|
|
591
651
|
|
|
592
652
|
# Process out of date modules
|
|
593
653
|
updated_modules = []
|
|
594
|
-
click.echo("Found {} module[s] needing update."
|
|
654
|
+
click.echo(f"Found {len(modules_to_update)} module[s] needing update.")
|
|
595
655
|
if not update_all:
|
|
596
656
|
click.echo("Please indicate which module[s] you wish to update:\n")
|
|
597
657
|
for module in modules_to_update:
|
|
@@ -630,22 +690,20 @@ def update(ctx, update_all): # pragma: no cover
|
|
|
630
690
|
update_flag = click.confirm("Do you want to update?")
|
|
631
691
|
elif module.major_update:
|
|
632
692
|
update_flag = click.confirm(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
"changes. Do you want to update?".format(module.name)
|
|
636
|
-
)
|
|
693
|
+
"'{}' is a Major Version update and may contain breaking "
|
|
694
|
+
"changes. Do you want to update?".format(module.name)
|
|
637
695
|
)
|
|
638
696
|
else:
|
|
639
|
-
update_flag = click.confirm("Update '{}'?"
|
|
697
|
+
update_flag = click.confirm(f"Update '{module.name}'?")
|
|
640
698
|
if update_flag:
|
|
641
699
|
# pylint: disable=broad-except
|
|
642
700
|
try:
|
|
643
701
|
ctx.obj["backend"].update(module)
|
|
644
702
|
updated_modules.append(module.name)
|
|
645
|
-
click.echo("Updated {
|
|
703
|
+
click.echo(f"Updated {module.name}")
|
|
646
704
|
except Exception as ex:
|
|
647
705
|
logger.exception(ex)
|
|
648
|
-
click.echo("Something went wrong, {} (check the logs)"
|
|
706
|
+
click.echo(f"Something went wrong, {str(ex)} (check the logs)")
|
|
649
707
|
# pylint: enable=broad-except
|
|
650
708
|
|
|
651
709
|
if not updated_modules:
|
|
@@ -728,6 +786,10 @@ def bundle_add(ctx, bundle):
|
|
|
728
786
|
)
|
|
729
787
|
return
|
|
730
788
|
|
|
789
|
+
if Bundle.offline:
|
|
790
|
+
click.secho("Cannot add new bundle when '--offline' flag is present.", fg="red")
|
|
791
|
+
return
|
|
792
|
+
|
|
731
793
|
bundles_dict = get_bundles_local_dict()
|
|
732
794
|
modified = False
|
|
733
795
|
for bundle_repo in bundle:
|
circup/lazy_metadata.py
CHANGED
circup/module.py
CHANGED
|
@@ -79,16 +79,8 @@ class Module:
|
|
|
79
79
|
self.max_version = compatibility[1]
|
|
80
80
|
# Figure out the bundle path.
|
|
81
81
|
self.bundle_path = None
|
|
82
|
-
if self.mpy:
|
|
83
|
-
# Byte compiled, now check CircuitPython version.
|
|
84
|
-
|
|
85
|
-
major_version = self.backend.get_circuitpython_version()[0].split(".")[0]
|
|
86
|
-
bundle_platform = "{}mpy".format(major_version)
|
|
87
|
-
else:
|
|
88
|
-
# Regular Python
|
|
89
|
-
bundle_platform = "py"
|
|
90
82
|
# module path in the bundle
|
|
91
|
-
search_path = bundle.lib_dir(
|
|
83
|
+
search_path = bundle.lib_dir(source=not self.mpy)
|
|
92
84
|
if self.file:
|
|
93
85
|
self.bundle_path = os.path.join(search_path, self.file)
|
|
94
86
|
else:
|
circup/shared.py
CHANGED
|
@@ -22,7 +22,10 @@ BAD_FILE_FORMAT = "Invalid"
|
|
|
22
22
|
DATA_DIR = appdirs.user_data_dir(appname="circup", appauthor="adafruit")
|
|
23
23
|
|
|
24
24
|
#: Module formats list (and the other form used in github files)
|
|
25
|
-
PLATFORMS = {"py": "py", "9mpy": "9.x-mpy", "10mpy": "10.x-mpy"}
|
|
25
|
+
PLATFORMS = {"py": "py", "8mpy": "8.x-mpy", "9mpy": "9.x-mpy", "10mpy": "10.x-mpy"}
|
|
26
|
+
|
|
27
|
+
#: CircuitPython platforms that are currently supported.
|
|
28
|
+
SUPPORTED_PLATFORMS = ["9mpy", "10mpy"]
|
|
26
29
|
|
|
27
30
|
#: Timeout for requests calls like get()
|
|
28
31
|
REQUESTS_TIMEOUT = 30
|
|
@@ -131,7 +134,7 @@ def extract_metadata(path, logger):
|
|
|
131
134
|
logger.info("%s", path)
|
|
132
135
|
if path.endswith(".py"):
|
|
133
136
|
result["mpy"] = False
|
|
134
|
-
with open(path,
|
|
137
|
+
with open(path, encoding="utf-8") as source_file:
|
|
135
138
|
content = source_file.read()
|
|
136
139
|
#: The regex used to extract ``__version__`` and ``__repo__`` assignments.
|
|
137
140
|
dunder_key_val = r"""(__\w+__)(?:\s*:\s*\w+)?\s*=\s*(?:['"]|\(\s)(.+)['"]"""
|
circup/wwshell/commands.py
CHANGED
|
@@ -115,7 +115,7 @@ def main( # pylint: disable=too-many-locals
|
|
|
115
115
|
verbose_handler.setLevel(logging.INFO)
|
|
116
116
|
verbose_handler.setFormatter(log_formatter)
|
|
117
117
|
logger.addHandler(verbose_handler)
|
|
118
|
-
click.echo("Logging to {}\n"
|
|
118
|
+
click.echo(f"Logging to {LOGFILE}\n")
|
|
119
119
|
else:
|
|
120
120
|
ctx.obj["verbose"] = False
|
|
121
121
|
|
|
@@ -137,7 +137,7 @@ def main( # pylint: disable=too-many-locals
|
|
|
137
137
|
click.secho("Could not find a connected CircuitPython device.", fg="red")
|
|
138
138
|
sys.exit(1)
|
|
139
139
|
else:
|
|
140
|
-
click.echo("Found device at {}."
|
|
140
|
+
click.echo(f"Found device at {device_path}.")
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
@main.command("ls")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circup
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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
|
|
@@ -42,7 +42,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
42
42
|
Classifier: Topic :: Education
|
|
43
43
|
Classifier: Topic :: Software Development :: Embedded Systems
|
|
44
44
|
Classifier: Topic :: System :: Software Distribution
|
|
45
|
-
Requires-Python: >=3.
|
|
45
|
+
Requires-Python: >=3.10
|
|
46
46
|
Description-Content-Type: text/x-rst
|
|
47
47
|
License-File: LICENSE
|
|
48
48
|
Requires-Dist: appdirs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
circup/__init__.py,sha256=9A98U3DyA14tm-7_d88fGSlLfEKz9T_85t8kXkErqRg,662
|
|
2
|
+
circup/backends.py,sha256=bm90_c3twTFZw0sDEXwytdV3LoJHUh9v01IDuzWKS5E,39812
|
|
3
|
+
circup/bundle.py,sha256=IdRCuV8smHGKbGkh-GDVfJ_Txkhuu4IwvighUJezn8c,9437
|
|
4
|
+
circup/command_utils.py,sha256=-3nK98EEg2d37y0tuZwAESPrq6sroPkMcoDqOfd40Xs,37087
|
|
5
|
+
circup/commands.py,sha256=gPj6S2eUc6xlLcB8MPlWXZRJ5fe9CCcWqMi7qyKhR10,33885
|
|
6
|
+
circup/lazy_metadata.py,sha256=GJXHkctUJyEzOCycmb7EPZT1GeES7rtX0w1tCtdtT6E,3791
|
|
7
|
+
circup/logging.py,sha256=hu4v8ljkXo8ru-cqs0W3PU-xEVvTO_qqMKDJM18OXbQ,1115
|
|
8
|
+
circup/module.py,sha256=ZrgZhBKuDWHfw9FzcXKjBYx-oybbDnH_IAdek3aSGwU,7131
|
|
9
|
+
circup/shared.py,sha256=yaAL61iJPSA98eJfjyAuOnxVhOFYJXvVZ8yE6aDOUjk,8747
|
|
10
|
+
circup/config/bundle_config.json,sha256=zzpmfy0jD7TxpOOw2P_gqIISuMBG0enb_f3_VneQ5mI,135
|
|
11
|
+
circup/config/bundle_config.json.license,sha256=OOHNqDsViGFhmG9z8J0o98hYmub1CkYKiZB96Php6KE,80
|
|
12
|
+
circup/wwshell/README.rst,sha256=M_jFP0hwOcngF0RdosdeqmVOISNcPzyjTW3duzIu9A8,3617
|
|
13
|
+
circup/wwshell/README.rst.license,sha256=GhA0SoZGP7CReDam-JJk_UtIQIpQaZWQFzR26YSuMm4,107
|
|
14
|
+
circup/wwshell/__init__.py,sha256=CAPZiYrouWboyPx4KiWLBG_vf_n0MmArGqaFyTXGKWk,398
|
|
15
|
+
circup/wwshell/commands.py,sha256=duMirWQqUDup6EqkeAgwdjcdccTaiZYTLTYjId_RBto,7438
|
|
16
|
+
circup-3.0.0.dist-info/licenses/LICENSE,sha256=bVlIMmSL_pqLCqae4hzixy9pYXD808IbgsMoQXTNLBk,1076
|
|
17
|
+
circup-3.0.0.dist-info/METADATA,sha256=bkXUohS9dz58rQ861CBrtQwqppDlvfGKt_9vN_mt9nA,13618
|
|
18
|
+
circup-3.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
19
|
+
circup-3.0.0.dist-info/entry_points.txt,sha256=FjTmwYD_ApgLRGifUrr_Ui1voW6fEzodQc3DKNzoAPc,69
|
|
20
|
+
circup-3.0.0.dist-info/top_level.txt,sha256=Qx6E0eZgSBE10ciNKsLZx8-TTy_9fEVZh7NLmn24KcU,7
|
|
21
|
+
circup-3.0.0.dist-info/RECORD,,
|
circup-2.3.0.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
circup/__init__.py,sha256=9A98U3DyA14tm-7_d88fGSlLfEKz9T_85t8kXkErqRg,662
|
|
2
|
-
circup/backends.py,sha256=g9Q9xCGZidwsEDL2Ga2cm50YYB54IiqlKUPcxj-pWZA,40008
|
|
3
|
-
circup/bundle.py,sha256=eQ_PxMaDD_XKfxXMXcpzSnhHjx5oJh1YXZq00o5smiM,7075
|
|
4
|
-
circup/command_utils.py,sha256=_DfnRKinXflkPt-CGaIg_3nBZ6Q2J9yRFS9LXC73k3U,35180
|
|
5
|
-
circup/commands.py,sha256=vZ4iui7IpLIkeaO_LQ5JzIqCPkfcUbrbUmICcLrl9aE,31494
|
|
6
|
-
circup/lazy_metadata.py,sha256=69VidxGGWE13QwAAtMCPNTXTsQ2q5dJvMtclw4YaqEY,3764
|
|
7
|
-
circup/logging.py,sha256=hu4v8ljkXo8ru-cqs0W3PU-xEVvTO_qqMKDJM18OXbQ,1115
|
|
8
|
-
circup/module.py,sha256=33_kdy5BZn6COyIjAFZMpw00rTtPiryQZWFXQkMF8FY,7435
|
|
9
|
-
circup/shared.py,sha256=O3_iA1mIsoVPObTzv5tfPCqejJSZitTWb0RMYIEM87w,8635
|
|
10
|
-
circup/config/bundle_config.json,sha256=zzpmfy0jD7TxpOOw2P_gqIISuMBG0enb_f3_VneQ5mI,135
|
|
11
|
-
circup/config/bundle_config.json.license,sha256=OOHNqDsViGFhmG9z8J0o98hYmub1CkYKiZB96Php6KE,80
|
|
12
|
-
circup/wwshell/README.rst,sha256=M_jFP0hwOcngF0RdosdeqmVOISNcPzyjTW3duzIu9A8,3617
|
|
13
|
-
circup/wwshell/README.rst.license,sha256=GhA0SoZGP7CReDam-JJk_UtIQIpQaZWQFzR26YSuMm4,107
|
|
14
|
-
circup/wwshell/__init__.py,sha256=CAPZiYrouWboyPx4KiWLBG_vf_n0MmArGqaFyTXGKWk,398
|
|
15
|
-
circup/wwshell/commands.py,sha256=-I5l7XeoDmvWWuZg5wHdt9qe__SBQ1EGmKwCDTBMeus,7454
|
|
16
|
-
circup-2.3.0.dist-info/licenses/LICENSE,sha256=bVlIMmSL_pqLCqae4hzixy9pYXD808IbgsMoQXTNLBk,1076
|
|
17
|
-
circup-2.3.0.dist-info/METADATA,sha256=0PRlXlC9q3DFjK27uGkOPIj3tNYxbhi7ciaL8epvyKQ,13617
|
|
18
|
-
circup-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
-
circup-2.3.0.dist-info/entry_points.txt,sha256=FjTmwYD_ApgLRGifUrr_Ui1voW6fEzodQc3DKNzoAPc,69
|
|
20
|
-
circup-2.3.0.dist-info/top_level.txt,sha256=Qx6E0eZgSBE10ciNKsLZx8-TTy_9fEVZh7NLmn24KcU,7
|
|
21
|
-
circup-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|