Sat, 26 Apr 2025 12:34:32 +0200
MicroPython
- Added a configuration option to disable the support for the no longer produced Pimoroni Pico Wireless Pack.
9740 | 1 | # -*- coding: utf-8 -*- |
2 | ||
11090
f5f5f5803935
Updated copyright for 2025.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11011
diff
changeset
|
3 | # Copyright (c) 2023 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> |
9740 | 4 | # |
5 | ||
6 | """ | |
7 | Module implementing an interface to the 'circup' package. | |
8 | """ | |
9 | ||
10 | import importlib | |
11 | import logging | |
12 | import os | |
13 | import re | |
14 | import shutil | |
15 | ||
16 | import requests | |
17 | ||
18 | from PyQt6.QtCore import QObject, pyqtSlot | |
19 | from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu | |
20 | ||
21 | from eric7 import Preferences | |
22 | from eric7.EricGui.EricOverrideCursor import EricOverrideCursor | |
23 | from eric7.EricWidgets import EricFileDialog, EricMessageBox | |
24 | from eric7.EricWidgets.EricApplication import ericApp | |
25 | from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog | |
26 | from eric7.SystemUtilities import PythonUtilities | |
27 | ||
28 | try: | |
29 | import circup | |
30 | ||
31 | circup.logger.setLevel(logging.WARNING) | |
32 | except ImportError: | |
33 | circup = None | |
34 | ||
35 | ||
36 | class CircuitPythonUpdaterInterface(QObject): | |
37 | """ | |
38 | Class implementing an interface to the 'circup' package. | |
39 | """ | |
40 | ||
41 | def __init__(self, device, parent=None): | |
42 | """ | |
43 | Constructor | |
44 | ||
45 | @param device reference to the CircuitPython device interface | |
46 | @type CircuitPythonDevice | |
47 | @param parent reference to the parent object (defaults to None) | |
48 | @type QObject (optional) | |
49 | """ | |
50 | super().__init__(parent) | |
51 | ||
52 | self.__device = device | |
53 | ||
54 | self.__installMenu = QMenu(self.tr("Install Modules")) | |
55 | self.__installMenu.setTearOffEnabled(True) | |
56 | self.__installMenu.addAction( | |
57 | self.tr("Select from Available Modules"), self.__installFromAvailable | |
58 | ) | |
59 | self.__installMenu.addAction( | |
60 | self.tr("Install Requirements"), self.__installRequirements | |
61 | ) | |
62 | self.__installMenu.addAction( | |
63 | self.tr("Install based on 'code.py'"), self.__installFromCode | |
64 | ) | |
65 | self.__installMenu.addSeparator() | |
66 | self.__installPyAct = self.__installMenu.addAction( | |
67 | self.tr("Install Python Source") | |
68 | ) | |
69 | self.__installPyAct.setCheckable(True) | |
70 | self.__installPyAct.setChecked(False) | |
71 | # kind of hack to make this action not hide the menu | |
72 | # Note: parent menus are hidden nevertheless | |
73 | self.__installPyAct.toggled.connect(self.__installMenu.show) | |
74 | ||
75 | def populateMenu(self, menu): | |
76 | """ | |
77 | Public method to populate the 'circup' menu. | |
78 | ||
79 | @param menu reference to the menu to be populated | |
80 | @type QMenu | |
81 | """ | |
82 | from .CircupFunctions import patch_circup | |
83 | ||
84 | patch_circup() | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
85 | isMounted = self.__device.supportsLocalFileAccess() |
9740 | 86 | |
87 | act = menu.addAction(self.tr("circup"), self.__aboutCircup) | |
88 | font = act.font() | |
89 | font.setBold(True) | |
90 | act.setFont(font) | |
91 | menu.addSeparator() | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
92 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
93 | self.tr("List Outdated Modules"), self.__listOutdatedModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
94 | ).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
95 | menu.addAction(self.tr("Update Modules"), self.__updateModules).setEnabled( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
96 | isMounted |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
97 | ) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
98 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
99 | self.tr("Update All Modules"), self.__updateAllModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
100 | ).setEnabled(isMounted) |
9740 | 101 | menu.addSeparator() |
102 | menu.addAction(self.tr("Show Available Modules"), self.__showAvailableModules) | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
103 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
104 | self.tr("Show Installed Modules"), self.__showInstalledModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
105 | ).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
106 | menu.addMenu(self.__installMenu).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
107 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
108 | self.tr("Uninstall Modules"), self.__uninstallModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
109 | ).setEnabled(isMounted) |
9740 | 110 | menu.addSeparator() |
111 | menu.addAction( | |
112 | self.tr("Generate Requirements ..."), self.__generateRequirements | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
113 | ).setEnabled(isMounted) |
9740 | 114 | menu.addSeparator() |
115 | menu.addAction(self.tr("Show Bundles"), self.__showBundles) | |
116 | menu.addAction(self.tr("Show Bundles with Modules"), self.__showBundlesModules) | |
117 | menu.addSeparator() | |
118 | menu.addAction(self.tr("Add Bundle"), self.__addBundle) | |
119 | menu.addAction(self.tr("Remove Bundles"), self.__removeBundle) | |
9869
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
120 | menu.addSeparator() |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
121 | menu.addAction(self.tr("Show Local Cache Path"), self.__showCachePath) |
9740 | 122 | |
123 | @pyqtSlot() | |
124 | def __aboutCircup(self): | |
125 | """ | |
126 | Private slot to show some info about 'circup'. | |
127 | """ | |
128 | version = circup.get_circup_version() | |
129 | if version is None: | |
130 | version = self.tr("unknown") | |
131 | ||
132 | EricMessageBox.information( | |
133 | None, | |
134 | self.tr("About circup"), | |
135 | self.tr( | |
136 | """<p><b>circup Version {0}</b></p>""" | |
137 | """<p><i>circup</i> is a tool to manage and update libraries on a""" | |
138 | """ CircuitPython device.</p>""", | |
139 | ).format(version), | |
140 | ) | |
141 | ||
142 | @pyqtSlot() | |
143 | def installCircup(self): | |
144 | """ | |
145 | Public slot to install the 'circup' package via pip. | |
146 | """ | |
147 | global circup | |
148 | ||
149 | pip = ericApp().getObject("Pip") | |
150 | pip.installPackages( | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
151 | ["circup>=2.0.0"], interpreter=PythonUtilities.getPythonExecutable() |
9740 | 152 | ) |
153 | ||
154 | circup = importlib.import_module("circup") | |
155 | circup.logger.setLevel(logging.WARNING) | |
156 | ||
157 | @pyqtSlot() | |
158 | def __showBundles(self, withModules=False): | |
159 | """ | |
160 | Private slot to show the available bundles (default and local). | |
161 | ||
162 | @param withModules flag indicating to list the modules and their version | |
163 | (defaults to False) | |
164 | @type bool (optional) | |
165 | """ | |
166 | from .ShowBundlesDialog import ShowBundlesDialog | |
167 | ||
168 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
169 | dlg = ShowBundlesDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
170 | withModules=withModules, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
171 | ) |
9740 | 172 | dlg.exec() |
173 | ||
174 | @pyqtSlot() | |
175 | def __showBundlesModules(self): | |
176 | """ | |
177 | Private slot to show the available bundles (default and local) with their | |
178 | modules. | |
179 | """ | |
180 | self.__showBundles(withModules=True) | |
181 | ||
182 | @pyqtSlot() | |
183 | def __addBundle(self): | |
184 | """ | |
185 | Private slot to add a bundle to the local bundles list, by "user/repo" github | |
186 | string. | |
187 | """ | |
188 | bundle, ok = QInputDialog.getText( | |
189 | None, | |
190 | self.tr("Add Bundle"), | |
191 | self.tr("Enter Bundle by 'User/Repo' Github String:"), | |
192 | QLineEdit.EchoMode.Normal, | |
193 | ) | |
194 | if ok and bundle: | |
195 | bundles = circup.get_bundles_local_dict() | |
196 | modified = False | |
197 | ||
198 | # do some cleanup | |
199 | bundle = re.sub(r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle) | |
200 | if bundle in bundles: | |
201 | EricMessageBox.information( | |
202 | None, | |
203 | self.tr("Add Bundle"), | |
204 | self.tr( | |
205 | """<p>The bundle <b>{0}</b> is already in the list.</p>""" | |
206 | ).format(bundle), | |
207 | ) | |
208 | return | |
209 | ||
210 | try: | |
211 | cBundle = circup.Bundle(bundle) | |
212 | except ValueError: | |
213 | EricMessageBox.critical( | |
214 | None, | |
215 | self.tr("Add Bundle"), | |
216 | self.tr( | |
217 | """<p>The bundle string is invalid, expecting github URL""" | |
218 | """ or 'user/repository' string.</p>""" | |
219 | ), | |
220 | ) | |
221 | return | |
222 | ||
10170
6cf1ee737d8f
Corrected some more code style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9870
diff
changeset
|
223 | result = requests.head("https://github.com/" + bundle, timeout=30) |
9740 | 224 | if result.status_code == requests.codes.NOT_FOUND: |
225 | EricMessageBox.critical( | |
226 | None, | |
227 | self.tr("Add Bundle"), | |
228 | self.tr( | |
229 | """<p>The bundle string is invalid. The repository doesn't""" | |
230 | """ exist (error code 404).</p>""" | |
231 | ), | |
232 | ) | |
233 | return | |
234 | ||
235 | if not cBundle.validate(): | |
236 | EricMessageBox.critical( | |
237 | None, | |
238 | self.tr("Add Bundle"), | |
239 | self.tr( | |
240 | """<p>The bundle string is invalid. Is the repository a valid""" | |
9741
901caff48307
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9740
diff
changeset
|
241 | """ circup bundle?</p>""" |
9740 | 242 | ), |
243 | ) | |
244 | return | |
245 | ||
246 | # Use the bundle string as the dictionary key for uniqueness | |
247 | bundles[bundle] = bundle | |
248 | modified = True | |
249 | EricMessageBox.information( | |
250 | None, | |
251 | self.tr("Add Bundle"), | |
252 | self.tr("""<p>Added bundle <b>{0}</b> ({1}).</p>""").format( | |
253 | bundle, cBundle.url | |
254 | ), | |
255 | ) | |
256 | ||
257 | if modified: | |
258 | # save the bundles list | |
259 | circup.save_local_bundles(bundles) | |
260 | # update and get the new bundle for the first time | |
261 | circup.get_bundle_versions(circup.get_bundles_list()) | |
262 | ||
263 | @pyqtSlot() | |
264 | def __removeBundle(self): | |
265 | """ | |
266 | Private slot to remove one or more bundles from the local bundles list. | |
267 | """ | |
268 | localBundles = circup.get_bundles_local_dict() | |
269 | dlg = EricListSelectionDialog( | |
10373
093dcebe5ecb
Corrected some uses of dict.keys(), dict.values() and dict.items().
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10170
diff
changeset
|
270 | sorted(localBundles), |
9740 | 271 | title=self.tr("Remove Bundles"), |
272 | message=self.tr("Select the bundles to be removed:"), | |
273 | checkBoxSelection=True, | |
11006
a671918232f3
Modified modal dialog usage to always include a valid parent (needed for Wayland).
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11005
diff
changeset
|
274 | parent=self.__device.microPython, |
9740 | 275 | ) |
276 | modified = False | |
277 | if dlg.exec() == QDialog.DialogCode.Accepted: | |
278 | bundles = dlg.getSelection() | |
279 | for bundle in bundles: | |
280 | del localBundles[bundle] | |
281 | modified = True | |
282 | ||
283 | if modified: | |
284 | circup.save_local_bundles(localBundles) | |
285 | EricMessageBox.information( | |
286 | None, | |
287 | self.tr("Remove Bundles"), | |
288 | self.tr( | |
289 | """<p>These bundles were removed from the local bundles list.{0}""" | |
290 | """</p>""" | |
291 | ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(bundles))), | |
292 | ) | |
293 | ||
294 | @pyqtSlot() | |
295 | def __listOutdatedModules(self): | |
296 | """ | |
297 | Private slot to list the outdated modules of the connected device. | |
298 | """ | |
299 | from .ShowOutdatedDialog import ShowOutdatedDialog | |
300 | ||
301 | devicePath = self.__device.getWorkspace() | |
302 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
303 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 304 | circup.CPY_VERSION = cpyVersion |
305 | ||
306 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
307 | dlg = ShowOutdatedDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
308 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
309 | ) |
9740 | 310 | dlg.exec() |
311 | ||
312 | @pyqtSlot() | |
313 | def __updateModules(self): | |
314 | """ | |
315 | Private slot to update the modules of the connected device. | |
316 | """ | |
317 | from .ShowOutdatedDialog import ShowOutdatedDialog | |
318 | ||
319 | devicePath = self.__device.getWorkspace() | |
320 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
321 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 322 | circup.CPY_VERSION = cpyVersion |
323 | ||
324 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
325 | dlg = ShowOutdatedDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
326 | devicePath=devicePath, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
327 | selectionMode=True, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
328 | parent=self.__device.microPython, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
329 | ) |
9740 | 330 | if dlg.exec() == QDialog.DialogCode.Accepted: |
331 | modules = dlg.getSelection() | |
332 | self.__doUpdateModules(modules) | |
333 | ||
334 | @pyqtSlot() | |
335 | def __updateAllModules(self): | |
336 | """ | |
337 | Private slot to update all modules of the connected device. | |
338 | """ | |
339 | devicePath = self.__device.getWorkspace() | |
340 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
341 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 342 | circup.CPY_VERSION = cpyVersion |
343 | ||
344 | with EricOverrideCursor(): | |
345 | modules = [ | |
346 | m | |
347 | for m in circup.find_modules(devicePath, circup.get_bundles_list()) | |
348 | if m.outofdate | |
349 | ] | |
350 | if modules: | |
351 | self.__doUpdateModules(modules) | |
352 | else: | |
353 | EricMessageBox.information( | |
354 | None, | |
355 | self.tr("Update Modules"), | |
356 | self.tr("All modules are already up-to-date."), | |
357 | ) | |
358 | ||
359 | def __doUpdateModules(self, modules): | |
360 | """ | |
361 | Private method to perform the update of a list of modules. | |
362 | ||
363 | @param modules list of modules to be updated | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
364 | @type circup.module.Module |
9740 | 365 | """ |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
366 | backend = circup.DiskBackend(self.__device.getWorkspace(), circup.logger) |
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
367 | |
9740 | 368 | updatedModules = [] |
369 | for module in modules: | |
370 | try: | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
371 | backend.update(module) |
9740 | 372 | updatedModules.append(module.name) |
373 | except Exception as ex: | |
374 | EricMessageBox.critical( | |
375 | None, | |
376 | self.tr("Update Modules"), | |
377 | self.tr( | |
378 | """<p>There was an error updating <b>{0}</b>.</p>""" | |
379 | """<p>Error: {1}</p>""" | |
380 | ).format(module.name, str(ex)), | |
381 | ) | |
382 | ||
383 | if updatedModules: | |
384 | EricMessageBox.information( | |
385 | None, | |
386 | self.tr("Update Modules"), | |
387 | self.tr( | |
388 | """<p>These modules were updated on the connected device.{0}</p>""" | |
389 | ).format( | |
390 | """<ul><li>{0}</li></ul>""".format("</li><li>".join(updatedModules)) | |
391 | ), | |
392 | ) | |
393 | else: | |
394 | EricMessageBox.information( | |
395 | None, | |
396 | self.tr("Update Modules"), | |
397 | self.tr("No modules could be updated."), | |
398 | ) | |
399 | ||
400 | @pyqtSlot() | |
401 | def __showAvailableModules(self): | |
402 | """ | |
403 | Private slot to show the available modules. | |
404 | ||
405 | These are modules which could be installed on the device. | |
406 | """ | |
9817 | 407 | from eric7.MicroPython.ShowModulesDialog import ShowModulesDialog |
9740 | 408 | |
409 | with EricOverrideCursor(): | |
9748 | 410 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
411 | moduleNames = [m.replace(".py", "") for m in availableModules] | |
412 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
413 | dlg = ShowModulesDialog(moduleNames, parent=self.__device.microPython) |
9740 | 414 | dlg.exec() |
415 | ||
416 | @pyqtSlot() | |
417 | def __showInstalledModules(self): | |
418 | """ | |
419 | Private slot to show the modules installed on the connected device. | |
420 | """ | |
421 | from .ShowInstalledDialog import ShowInstalledDialog | |
422 | ||
423 | devicePath = self.__device.getWorkspace() | |
424 | ||
425 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
426 | dlg = ShowInstalledDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
427 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
428 | ) |
9740 | 429 | dlg.exec() |
430 | ||
431 | @pyqtSlot() | |
432 | def __installFromAvailable(self): | |
433 | """ | |
434 | Private slot to install modules onto the connected device. | |
435 | """ | |
9817 | 436 | from eric7.MicroPython.ShowModulesDialog import ShowModulesDialog |
9740 | 437 | |
438 | with EricOverrideCursor(): | |
9748 | 439 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
440 | moduleNames = [m.replace(".py", "") for m in availableModules] | |
441 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
442 | dlg = ShowModulesDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
443 | moduleNames, selectionMode=True, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
444 | ) |
9740 | 445 | if dlg.exec() == QDialog.DialogCode.Accepted: |
446 | modules = dlg.getSelection() | |
447 | self.__installModules(modules) | |
448 | ||
449 | @pyqtSlot() | |
450 | def __installRequirements(self): | |
451 | """ | |
452 | Private slot to install modules determined by a requirements file. | |
453 | """ | |
454 | homeDir = ( | |
455 | Preferences.getMicroPython("MpyWorkspace") | |
456 | or Preferences.getMultiProject("Workspace") | |
457 | or os.path.expanduser("~") | |
458 | ) | |
459 | reqFile = EricFileDialog.getOpenFileName( | |
460 | None, | |
461 | self.tr("Install Modules"), | |
462 | homeDir, | |
463 | self.tr("Text Files (*.txt);;All Files (*)"), | |
464 | ) | |
465 | if reqFile: | |
466 | if os.path.exists(reqFile): | |
467 | with open(reqFile, "r") as fp: | |
468 | requirementsText = fp.read() | |
469 | modules = circup.libraries_from_requirements(requirementsText) | |
470 | if modules: | |
471 | self.__installModules(modules) | |
472 | else: | |
473 | EricMessageBox.critical( | |
474 | None, | |
475 | self.tr("Install Modules"), | |
476 | self.tr( | |
477 | """<p>The given requirements file <b>{0}</b> does not""" | |
478 | """ contain valid modules.</p>""" | |
479 | ).format(reqFile), | |
480 | ) | |
481 | else: | |
482 | EricMessageBox.critical( | |
483 | None, | |
484 | self.tr("Install Modules"), | |
485 | self.tr( | |
486 | """<p>The given requirements file <b>{0}</b> does not exist.""" | |
487 | """</p>""" | |
488 | ).format(reqFile), | |
489 | ) | |
490 | ||
491 | @pyqtSlot() | |
492 | def __installFromCode(self): | |
493 | """ | |
494 | Private slot to install modules based on the 'code.py' file of the | |
495 | connected device. | |
496 | """ | |
497 | devicePath = self.__device.getWorkspace() | |
498 | ||
499 | codeFile = EricFileDialog.getOpenFileName( | |
500 | None, | |
501 | self.tr("Install Modules"), | |
502 | os.path.join(devicePath, "code.py"), | |
503 | self.tr("Python Files (*.py);;All Files (*)"), | |
504 | ) | |
505 | if codeFile: | |
506 | if os.path.exists(codeFile): | |
507 | with EricOverrideCursor(): | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
508 | availableModules = circup.command_utils.get_bundle_versions( |
9740 | 509 | circup.get_bundles_list() |
510 | ) | |
511 | moduleNames = {} | |
512 | for module, metadata in availableModules.items(): | |
513 | moduleNames[module.replace(".py", "")] = metadata | |
514 | ||
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
515 | modules = circup.libraries_from_code_py(codeFile, moduleNames) |
9740 | 516 | if modules: |
517 | self.__installModules(modules) | |
518 | else: | |
519 | EricMessageBox.critical( | |
520 | None, | |
521 | self.tr("Install Modules"), | |
522 | self.tr( | |
523 | """<p>The given code file <b>{0}</b> does not""" | |
524 | """ contain valid import statements or does not import""" | |
525 | """ external modules.</p>""" | |
526 | ).format(codeFile), | |
527 | ) | |
528 | else: | |
529 | EricMessageBox.critical( | |
530 | None, | |
531 | self.tr("Install Modules"), | |
532 | self.tr( | |
533 | """<p>The given code file <b>{0}</b> does not exist.</p>""" | |
534 | ).format(codeFile), | |
535 | ) | |
536 | ||
537 | def __installModules(self, installs): | |
538 | """ | |
539 | Private method to install the given list of modules. | |
540 | ||
541 | @param installs list of module names to be installed | |
542 | @type list of str | |
543 | """ | |
544 | devicePath = self.__device.getWorkspace() | |
10900 | 545 | backend = circup.DiskBackend(devicePath, circup.logger) |
9740 | 546 | |
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
547 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 548 | circup.CPY_VERSION = cpyVersion |
549 | ||
550 | with EricOverrideCursor(): | |
551 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) | |
552 | moduleNames = {} | |
553 | for module, metadata in availableModules.items(): | |
554 | moduleNames[module.replace(".py", "")] = metadata | |
555 | toBeInstalled = circup.get_dependencies(installs, mod_names=moduleNames) | |
10900 | 556 | deviceModules = backend.get_device_versions() |
9740 | 557 | if toBeInstalled is not None: |
558 | dependencies = [m for m in toBeInstalled if m not in installs] | |
559 | ok = EricMessageBox.yesNo( | |
560 | None, | |
561 | self.tr("Install Modules"), | |
562 | self.tr("""<p>Ready to install these modules?{0}{1}</p>""").format( | |
563 | """<ul><li>{0}</li></ul>""".format( | |
564 | "</li><li>".join(sorted(installs)) | |
565 | ), | |
10621
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
566 | ( |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
567 | self.tr("Dependencies:") |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
568 | + """<ul><li>{0}</li></ul>""".format( |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
569 | "</li><li>".join(sorted(dependencies)) |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
570 | ) |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
571 | if dependencies |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
572 | else "" |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
573 | ), |
9740 | 574 | ), |
575 | yesDefault=True, | |
576 | ) | |
577 | if ok: | |
578 | installedModules = [] | |
579 | with EricOverrideCursor(): | |
580 | for library in toBeInstalled: | |
581 | success = circup.install_module( | |
582 | devicePath, | |
583 | deviceModules, | |
584 | library, | |
585 | self.__installPyAct.isChecked(), | |
586 | moduleNames, | |
587 | ) | |
588 | if success: | |
589 | installedModules.append(library) | |
590 | ||
591 | if installedModules: | |
592 | EricMessageBox.information( | |
593 | None, | |
594 | self.tr("Install Modules"), | |
595 | self.tr( | |
596 | "<p>Installation complete. These modules were installed" | |
597 | " successfully.{0}</p>" | |
598 | ).format( | |
599 | """<ul><li>{0}</li></ul>""".format( | |
600 | "</li><li>".join(sorted(installedModules)) | |
601 | ), | |
602 | ), | |
603 | ) | |
604 | else: | |
605 | EricMessageBox.information( | |
606 | None, | |
607 | self.tr("Install Modules"), | |
608 | self.tr( | |
609 | "<p>Installation complete. No modules were installed.</p>" | |
610 | ), | |
611 | ) | |
612 | else: | |
613 | EricMessageBox.information( | |
614 | None, | |
615 | self.tr("Install Modules"), | |
616 | self.tr("<p>No modules installation is required.</p>"), | |
617 | ) | |
618 | ||
619 | @pyqtSlot() | |
620 | def __uninstallModules(self): | |
621 | """ | |
622 | Private slot to uninstall modules from the connected device. | |
623 | """ | |
624 | devicePath = self.__device.getWorkspace() | |
625 | libraryPath = os.path.join(devicePath, "lib") | |
626 | ||
627 | with EricOverrideCursor(): | |
10900 | 628 | backend = circup.DiskBackend(devicePath, circup.logger) |
629 | deviceModules = backend.get_device_versions() | |
9740 | 630 | modNames = {} |
631 | for moduleItem, metadata in deviceModules.items(): | |
632 | modNames[moduleItem.replace(".py", "").lower()] = metadata | |
633 | ||
634 | dlg = EricListSelectionDialog( | |
10373
093dcebe5ecb
Corrected some uses of dict.keys(), dict.values() and dict.items().
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10170
diff
changeset
|
635 | sorted(modNames), |
9740 | 636 | title=self.tr("Uninstall Modules"), |
637 | message=self.tr("Select the modules/packages to be uninstalled:"), | |
638 | checkBoxSelection=True, | |
11006
a671918232f3
Modified modal dialog usage to always include a valid parent (needed for Wayland).
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11005
diff
changeset
|
639 | parent=self.__device.microPython, |
9740 | 640 | ) |
641 | if dlg.exec() == QDialog.DialogCode.Accepted: | |
642 | names = dlg.getSelection() | |
643 | for name in names: | |
644 | modulePath = modNames[name]["path"] | |
645 | if os.path.isdir(modulePath): | |
646 | target = os.path.basename(os.path.dirname(modulePath)) | |
647 | targetPath = os.path.join(libraryPath, target) | |
648 | # Remove the package directory. | |
649 | shutil.rmtree(targetPath) | |
650 | else: | |
651 | target = os.path.basename(modulePath) | |
652 | targetPath = os.path.join(libraryPath, target) | |
653 | # Remove the module file | |
654 | os.remove(targetPath) | |
655 | ||
656 | EricMessageBox.information( | |
657 | None, | |
658 | self.tr("Uninstall Modules"), | |
659 | self.tr( | |
660 | """<p>These modules/packages were uninstalled from the connected""" | |
661 | """ device.{0}</p>""" | |
662 | ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(names))), | |
663 | ) | |
664 | ||
665 | @pyqtSlot() | |
666 | def __generateRequirements(self): | |
667 | """ | |
668 | Private slot to generate requirements for the connected device. | |
669 | """ | |
670 | from .RequirementsDialog import RequirementsDialog | |
671 | ||
672 | devicePath = self.__device.getWorkspace() | |
673 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
674 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 675 | circup.CPY_VERSION = cpyVersion |
676 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
677 | dlg = RequirementsDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
678 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
679 | ) |
9740 | 680 | dlg.exec() |
681 | ||
9869
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
682 | @pyqtSlot() |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
683 | def __showCachePath(self): |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
684 | """ |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
685 | Private slot to show the path used by 'circup' to store the downloaded bundles. |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
686 | """ |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
687 | EricMessageBox.information( |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
688 | None, |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
689 | self.tr("Show Local Cache Path"), |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
690 | self.tr( |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
691 | "<p><b>circup</b> stores the downloaded CircuitPython bundles in this" |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
692 | " directory.</p><p>{0}</p>" |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
693 | ).format(circup.DATA_DIR), |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
694 | ) |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
695 | |
9740 | 696 | |
697 | def isCircupAvailable(): | |
698 | """ | |
699 | Function to check for the availability of 'circup'. | |
700 | ||
701 | @return flag indicating the availability of 'circup' | |
702 | @rtype bool | |
703 | """ | |
704 | global circup | |
705 | ||
706 | return circup is not None |