src/eric7/PipInterface/PipPackagesWidget.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9234
97bdad5be46d
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 16
17 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery 17 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery
18 from PyQt6.QtGui import QIcon 18 from PyQt6.QtGui import QIcon
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest 19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
20 from PyQt6.QtWidgets import ( 20 from PyQt6.QtWidgets import (
21 QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, 21 QWidget,
22 QMenu, QDialog, QAbstractItemView 22 QToolButton,
23 QApplication,
24 QHeaderView,
25 QTreeWidgetItem,
26 QMenu,
27 QDialog,
28 QAbstractItemView,
23 ) 29 )
24 30
25 from EricWidgets.EricApplication import ericApp 31 from EricWidgets.EricApplication import ericApp
26 from EricWidgets import EricMessageBox 32 from EricWidgets import EricMessageBox
27 from EricGui.EricOverrideCursor import EricOverrideCursor 33 from EricGui.EricOverrideCursor import EricOverrideCursor
36 42
37 class PypiSearchResultsParser(html.parser.HTMLParser): 43 class PypiSearchResultsParser(html.parser.HTMLParser):
38 """ 44 """
39 Class implementing the parser for the PyPI search result page. 45 Class implementing the parser for the PyPI search result page.
40 """ 46 """
47
41 ClassPrefix = "package-snippet__" 48 ClassPrefix = "package-snippet__"
42 49
43 def __init__(self, data): 50 def __init__(self, data):
44 """ 51 """
45 Constructor 52 Constructor
46 53
47 @param data data to be parsed 54 @param data data to be parsed
48 @type str 55 @type str
49 """ 56 """
50 super().__init__() 57 super().__init__()
51 self.__results = [] 58 self.__results = []
52 self.__activeClass = None 59 self.__activeClass = None
53 self.feed(data) 60 self.feed(data)
54 61
55 def __getClass(self, attrs): 62 def __getClass(self, attrs):
56 """ 63 """
57 Private method to extract the class attribute out of the list of 64 Private method to extract the class attribute out of the list of
58 attributes. 65 attributes.
59 66
60 @param attrs list of tag attributes as (name, value) tuples 67 @param attrs list of tag attributes as (name, value) tuples
61 @type list of tuple of (str, str) 68 @type list of tuple of (str, str)
62 @return value of the 'class' attribute or None 69 @return value of the 'class' attribute or None
63 @rtype str 70 @rtype str
64 """ 71 """
65 for name, value in attrs: 72 for name, value in attrs:
66 if name == "class": 73 if name == "class":
67 return value 74 return value
68 75
69 return None 76 return None
70 77
71 def __getDate(self, attrs): 78 def __getDate(self, attrs):
72 """ 79 """
73 Private method to extract the datetime attribute out of the list of 80 Private method to extract the datetime attribute out of the list of
74 attributes and process it. 81 attributes and process it.
75 82
76 @param attrs list of tag attributes as (name, value) tuples 83 @param attrs list of tag attributes as (name, value) tuples
77 @type list of tuple of (str, str) 84 @type list of tuple of (str, str)
78 @return value of the 'class' attribute or None 85 @return value of the 'class' attribute or None
79 @rtype str 86 @rtype str
80 """ 87 """
81 for name, value in attrs: 88 for name, value in attrs:
82 if name == "datetime": 89 if name == "datetime":
83 return value.split("T")[0] 90 return value.split("T")[0]
84 91
85 return None 92 return None
86 93
87 def handle_starttag(self, tag, attrs): 94 def handle_starttag(self, tag, attrs):
88 """ 95 """
89 Public method to process the start tag. 96 Public method to process the start tag.
90 97
91 @param tag tag name (all lowercase) 98 @param tag tag name (all lowercase)
92 @type str 99 @type str
93 @param attrs list of tag attributes as (name, value) tuples 100 @param attrs list of tag attributes as (name, value) tuples
94 @type list of tuple of (str, str) 101 @type list of tuple of (str, str)
95 """ 102 """
96 if tag == "a" and self.__getClass(attrs) == "package-snippet": 103 if tag == "a" and self.__getClass(attrs) == "package-snippet":
97 self.__results.append({}) 104 self.__results.append({})
98 105
99 if tag in ("span", "p"): 106 if tag in ("span", "p"):
100 tagClass = self.__getClass(attrs) 107 tagClass = self.__getClass(attrs)
101 if tagClass in ( 108 if tagClass in (
102 "package-snippet__name", "package-snippet__description", 109 "package-snippet__name",
103 "package-snippet__version", "package-snippet__released", 110 "package-snippet__description",
111 "package-snippet__version",
112 "package-snippet__released",
104 "package-snippet__created", 113 "package-snippet__created",
105 ): 114 ):
106 self.__activeClass = tagClass 115 self.__activeClass = tagClass
107 else: 116 else:
108 self.__activeClass = None 117 self.__activeClass = None
110 attributeName = self.__activeClass.replace(self.ClassPrefix, "") 119 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
111 self.__results[-1][attributeName] = self.__getDate(attrs) 120 self.__results[-1][attributeName] = self.__getDate(attrs)
112 self.__activeClass = None 121 self.__activeClass = None
113 else: 122 else:
114 self.__activeClass = None 123 self.__activeClass = None
115 124
116 def handle_data(self, data): 125 def handle_data(self, data):
117 """ 126 """
118 Public method process arbitrary data. 127 Public method process arbitrary data.
119 128
120 @param data data to be processed 129 @param data data to be processed
121 @type str 130 @type str
122 """ 131 """
123 if self.__activeClass is not None: 132 if self.__activeClass is not None:
124 attributeName = self.__activeClass.replace(self.ClassPrefix, "") 133 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
125 self.__results[-1][attributeName] = data 134 self.__results[-1][attributeName] = data
126 135
127 def handle_endtag(self, tag): 136 def handle_endtag(self, tag):
128 """ 137 """
129 Public method to process the end tag. 138 Public method to process the end tag.
130 139
131 @param tag tag name (all lowercase) 140 @param tag tag name (all lowercase)
132 @type str 141 @type str
133 """ 142 """
134 self.__activeClass = None 143 self.__activeClass = None
135 144
136 def getResults(self): 145 def getResults(self):
137 """ 146 """
138 Public method to get the extracted search results. 147 Public method to get the extracted search results.
139 148
140 @return extracted result data 149 @return extracted result data
141 @rtype list of dict 150 @rtype list of dict
142 """ 151 """
143 return self.__results 152 return self.__results
144 153
145 154
146 class PipPackagesWidget(QWidget, Ui_PipPackagesWidget): 155 class PipPackagesWidget(QWidget, Ui_PipPackagesWidget):
147 """ 156 """
148 Class implementing the pip packages management widget. 157 Class implementing the pip packages management widget.
149 """ 158 """
159
150 ShowProcessGeneralMode = 0 160 ShowProcessGeneralMode = 0
151 ShowProcessClassifiersMode = 1 161 ShowProcessClassifiersMode = 1
152 ShowProcessEntryPointsMode = 2 162 ShowProcessEntryPointsMode = 2
153 ShowProcessFilesListMode = 3 163 ShowProcessFilesListMode = 3
154 164
155 SearchVersionRole = Qt.ItemDataRole.UserRole + 1 165 SearchVersionRole = Qt.ItemDataRole.UserRole + 1
156 VulnerabilityRole = Qt.ItemDataRole.UserRole + 2 166 VulnerabilityRole = Qt.ItemDataRole.UserRole + 2
157 167
158 PackageColumn = 0 168 PackageColumn = 0
159 InstalledVersionColumn = 1 169 InstalledVersionColumn = 1
160 AvailableVersionColumn = 2 170 AvailableVersionColumn = 2
161 VulnerabilityColumn = 3 171 VulnerabilityColumn = 3
162 172
163 DepPackageColumn = 0 173 DepPackageColumn = 0
164 DepInstalledVersionColumn = 1 174 DepInstalledVersionColumn = 1
165 DepRequiredVersionColumn = 2 175 DepRequiredVersionColumn = 2
166 176
167 def __init__(self, pip, parent=None): 177 def __init__(self, pip, parent=None):
168 """ 178 """
169 Constructor 179 Constructor
170 180
171 @param pip reference to the global pip interface 181 @param pip reference to the global pip interface
172 @type Pip 182 @type Pip
173 @param parent reference to the parent widget 183 @param parent reference to the parent widget
174 @type QWidget 184 @type QWidget
175 """ 185 """
176 super().__init__(parent) 186 super().__init__(parent)
177 self.setupUi(self) 187 self.setupUi(self)
178 188
179 self.layout().setContentsMargins(0, 3, 0, 0) 189 self.layout().setContentsMargins(0, 3, 0, 0)
180 190
181 self.viewToggleButton.setIcon(UI.PixmapCache.getIcon("viewListTree")) 191 self.viewToggleButton.setIcon(UI.PixmapCache.getIcon("viewListTree"))
182 192
183 self.pipMenuButton.setObjectName( 193 self.pipMenuButton.setObjectName("pip_supermenu_button")
184 "pip_supermenu_button")
185 self.pipMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) 194 self.pipMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu"))
186 self.pipMenuButton.setToolTip(self.tr("pip Menu")) 195 self.pipMenuButton.setToolTip(self.tr("pip Menu"))
187 self.pipMenuButton.setPopupMode( 196 self.pipMenuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
188 QToolButton.ToolButtonPopupMode.InstantPopup) 197 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
189 self.pipMenuButton.setToolButtonStyle(
190 Qt.ToolButtonStyle.ToolButtonIconOnly)
191 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) 198 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
192 self.pipMenuButton.setAutoRaise(True) 199 self.pipMenuButton.setAutoRaise(True)
193 self.pipMenuButton.setShowMenuInside(True) 200 self.pipMenuButton.setShowMenuInside(True)
194 201
195 self.refreshButton.setIcon( 202 self.refreshButton.setIcon(UI.PixmapCache.getIcon("reload"))
196 UI.PixmapCache.getIcon("reload")) 203 self.upgradeButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
197 self.upgradeButton.setIcon( 204 self.upgradeAllButton.setIcon(UI.PixmapCache.getIcon("2uparrow"))
198 UI.PixmapCache.getIcon("1uparrow")) 205 self.uninstallButton.setIcon(UI.PixmapCache.getIcon("minus"))
199 self.upgradeAllButton.setIcon( 206 self.showPackageDetailsButton.setIcon(UI.PixmapCache.getIcon("info"))
200 UI.PixmapCache.getIcon("2uparrow")) 207 self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find"))
201 self.uninstallButton.setIcon( 208 self.searchButton.setIcon(UI.PixmapCache.getIcon("findNext"))
202 UI.PixmapCache.getIcon("minus")) 209 self.searchMoreButton.setIcon(UI.PixmapCache.getIcon("plus"))
203 self.showPackageDetailsButton.setIcon( 210 self.installButton.setIcon(UI.PixmapCache.getIcon("plus"))
204 UI.PixmapCache.getIcon("info")) 211 self.installUserSiteButton.setIcon(UI.PixmapCache.getIcon("addUser"))
205 self.searchToggleButton.setIcon( 212 self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info"))
206 UI.PixmapCache.getIcon("find")) 213
207 self.searchButton.setIcon( 214 self.refreshDependenciesButton.setIcon(UI.PixmapCache.getIcon("reload"))
208 UI.PixmapCache.getIcon("findNext")) 215 self.showDepPackageDetailsButton.setIcon(UI.PixmapCache.getIcon("info"))
209 self.searchMoreButton.setIcon( 216
210 UI.PixmapCache.getIcon("plus"))
211 self.installButton.setIcon(
212 UI.PixmapCache.getIcon("plus"))
213 self.installUserSiteButton.setIcon(
214 UI.PixmapCache.getIcon("addUser"))
215 self.showDetailsButton.setIcon(
216 UI.PixmapCache.getIcon("info"))
217
218 self.refreshDependenciesButton.setIcon(
219 UI.PixmapCache.getIcon("reload"))
220 self.showDepPackageDetailsButton.setIcon(
221 UI.PixmapCache.getIcon("info"))
222
223 self.__pip = pip 217 self.__pip = pip
224 218
225 self.packagesList.header().setSortIndicator( 219 self.packagesList.header().setSortIndicator(
226 PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder) 220 PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder
221 )
227 self.dependenciesList.header().setSortIndicator( 222 self.dependenciesList.header().setSortIndicator(
228 PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder) 223 PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder
229 224 )
225
230 self.__infoLabels = { 226 self.__infoLabels = {
231 "name": self.tr("Name:"), 227 "name": self.tr("Name:"),
232 "version": self.tr("Version:"), 228 "version": self.tr("Version:"),
233 "location": self.tr("Location:"), 229 "location": self.tr("Location:"),
234 "requires": self.tr("Requires:"), 230 "requires": self.tr("Requires:"),
243 "entry-points": self.tr("Entry Points:"), 239 "entry-points": self.tr("Entry Points:"),
244 "files": self.tr("Files:"), 240 "files": self.tr("Files:"),
245 } 241 }
246 self.infoWidget.setHeaderLabels(["Key", "Value"]) 242 self.infoWidget.setHeaderLabels(["Key", "Value"])
247 self.dependencyInfoWidget.setHeaderLabels(["Key", "Value"]) 243 self.dependencyInfoWidget.setHeaderLabels(["Key", "Value"])
248 244
249 venvManager = ericApp().getObject("VirtualEnvManager") 245 venvManager = ericApp().getObject("VirtualEnvManager")
250 venvManager.virtualEnvironmentAdded.connect( 246 venvManager.virtualEnvironmentAdded.connect(self.on_refreshButton_clicked)
251 self.on_refreshButton_clicked) 247 venvManager.virtualEnvironmentRemoved.connect(self.on_refreshButton_clicked)
252 venvManager.virtualEnvironmentRemoved.connect(
253 self.on_refreshButton_clicked)
254 self.__selectedEnvironment = None 248 self.__selectedEnvironment = None
255 249
256 project = ericApp().getObject("Project") 250 project = ericApp().getObject("Project")
257 project.projectOpened.connect( 251 project.projectOpened.connect(self.__projectOpened)
258 self.__projectOpened) 252 project.projectClosed.connect(self.__projectClosed)
259 project.projectClosed.connect( 253
260 self.__projectClosed)
261
262 self.__initPipMenu() 254 self.__initPipMenu()
263 self.__populateEnvironments() 255 self.__populateEnvironments()
264 self.__updateActionButtons() 256 self.__updateActionButtons()
265 self.__updateDepActionButtons() 257 self.__updateDepActionButtons()
266 258
267 self.statusLabel.hide() 259 self.statusLabel.hide()
268 self.searchWidget.hide() 260 self.searchWidget.hide()
269 self.__lastSearchPage = 0 261 self.__lastSearchPage = 0
270 262
271 self.__queryName = [] 263 self.__queryName = []
272 self.__querySummary = [] 264 self.__querySummary = []
273 265
274 self.__replies = [] 266 self.__replies = []
275 267
276 self.__packageDetailsDialog = None 268 self.__packageDetailsDialog = None
277 269
278 self.viewsStackWidget.setCurrentWidget(self.packagesPage) 270 self.viewsStackWidget.setCurrentWidget(self.packagesPage)
279 271
280 @pyqtSlot() 272 @pyqtSlot()
281 def __projectOpened(self): 273 def __projectOpened(self):
282 """ 274 """
283 Private slot to handle the projectOpened signal. 275 Private slot to handle the projectOpened signal.
284 """ 276 """
285 projectVenv = self.__pip.getProjectEnvironmentString() 277 projectVenv = self.__pip.getProjectEnvironmentString()
286 if projectVenv: 278 if projectVenv:
287 self.environmentsComboBox.insertItem(1, projectVenv) 279 self.environmentsComboBox.insertItem(1, projectVenv)
288 280
289 @pyqtSlot(bool) 281 @pyqtSlot(bool)
290 def __projectClosed(self, shutdown): 282 def __projectClosed(self, shutdown):
291 """ 283 """
292 Private slot to handle the projectClosed signal. 284 Private slot to handle the projectClosed signal.
293 285
294 @param shutdown flag indicating the IDE shutdown 286 @param shutdown flag indicating the IDE shutdown
295 @type bool 287 @type bool
296 """ 288 """
297 if not shutdown: 289 if not shutdown:
298 # the project entry is always at index 1 290 # the project entry is always at index 1
299 self.environmentsComboBox.removeItem(1) 291 self.environmentsComboBox.removeItem(1)
300 292
301 def __populateEnvironments(self): 293 def __populateEnvironments(self):
302 """ 294 """
303 Private method to get a list of environments and populate the selector. 295 Private method to get a list of environments and populate the selector.
304 """ 296 """
305 self.environmentsComboBox.addItem("") 297 self.environmentsComboBox.addItem("")
306 projectVenv = self.__pip.getProjectEnvironmentString() 298 projectVenv = self.__pip.getProjectEnvironmentString()
307 if projectVenv: 299 if projectVenv:
308 self.environmentsComboBox.addItem(projectVenv) 300 self.environmentsComboBox.addItem(projectVenv)
309 self.environmentsComboBox.addItems( 301 self.environmentsComboBox.addItems(
310 self.__pip.getVirtualenvNames( 302 self.__pip.getVirtualenvNames(
311 noRemote=True, 303 noRemote=True, noConda=Preferences.getPip("ExcludeCondaEnvironments")
312 noConda=Preferences.getPip("ExcludeCondaEnvironments") 304 )
313 ) 305 )
314 ) 306
315
316 def __isPipAvailable(self): 307 def __isPipAvailable(self):
317 """ 308 """
318 Private method to check, if the pip package is available for the 309 Private method to check, if the pip package is available for the
319 selected environment. 310 selected environment.
320 311
321 @return flag indicating availability 312 @return flag indicating availability
322 @rtype bool 313 @rtype bool
323 """ 314 """
324 available = False 315 available = False
325 316
326 venvName = self.environmentsComboBox.currentText() 317 venvName = self.environmentsComboBox.currentText()
327 if venvName: 318 if venvName:
328 available = ( 319 available = (
329 len(self.packagesList.findItems( 320 len(
330 "pip", 321 self.packagesList.findItems(
331 Qt.MatchFlag.MatchExactly | 322 "pip",
332 Qt.MatchFlag.MatchCaseSensitive)) == 1 323 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
333 ) 324 )
334 325 )
326 == 1
327 )
328
335 return available 329 return available
336 330
337 def __availablePipVersion(self): 331 def __availablePipVersion(self):
338 """ 332 """
339 Private method to get the pip version of the selected environment. 333 Private method to get the pip version of the selected environment.
340 334
341 @return tuple containing the version number or tuple with all zeros 335 @return tuple containing the version number or tuple with all zeros
342 in case pip is not available 336 in case pip is not available
343 @rtype tuple of int 337 @rtype tuple of int
344 """ 338 """
345 pipVersionTuple = (0, 0, 0) 339 pipVersionTuple = (0, 0, 0)
346 venvName = self.environmentsComboBox.currentText() 340 venvName = self.environmentsComboBox.currentText()
347 if venvName: 341 if venvName:
348 pipList = self.packagesList.findItems( 342 pipList = self.packagesList.findItems(
349 "pip", 343 "pip", Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive
350 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive
351 ) 344 )
352 if len(pipList) > 0: 345 if len(pipList) > 0:
353 pipVersionTuple = Globals.versionToTuple( 346 pipVersionTuple = Globals.versionToTuple(
354 pipList[0].text(PipPackagesWidget.InstalledVersionColumn)) 347 pipList[0].text(PipPackagesWidget.InstalledVersionColumn)
355 348 )
349
356 return pipVersionTuple 350 return pipVersionTuple
357 351
358 def getPip(self): 352 def getPip(self):
359 """ 353 """
360 Public method to get a reference to the pip interface object. 354 Public method to get a reference to the pip interface object.
361 355
362 @return reference to the pip interface object 356 @return reference to the pip interface object
363 @rtype Pip 357 @rtype Pip
364 """ 358 """
365 return self.__pip 359 return self.__pip
366 360
367 ####################################################################### 361 #######################################################################
368 ## Slots handling widget signals below 362 ## Slots handling widget signals below
369 ####################################################################### 363 #######################################################################
370 364
371 def __selectedUpdateableItems(self): 365 def __selectedUpdateableItems(self):
372 """ 366 """
373 Private method to get a list of selected items that can be updated. 367 Private method to get a list of selected items that can be updated.
374 368
375 @return list of selected items that can be updated 369 @return list of selected items that can be updated
376 @rtype list of QTreeWidgetItem 370 @rtype list of QTreeWidgetItem
377 """ 371 """
378 return [ 372 return [
379 itm for itm in self.packagesList.selectedItems() 373 itm
374 for itm in self.packagesList.selectedItems()
380 if bool(itm.text(PipPackagesWidget.AvailableVersionColumn)) 375 if bool(itm.text(PipPackagesWidget.AvailableVersionColumn))
381 ] 376 ]
382 377
383 def __allUpdateableItems(self): 378 def __allUpdateableItems(self):
384 """ 379 """
385 Private method to get a list of all items that can be updated. 380 Private method to get a list of all items that can be updated.
386 381
387 @return list of all items that can be updated 382 @return list of all items that can be updated
388 @rtype list of QTreeWidgetItem 383 @rtype list of QTreeWidgetItem
389 """ 384 """
390 updateableItems = [] 385 updateableItems = []
391 for index in range(self.packagesList.topLevelItemCount()): 386 for index in range(self.packagesList.topLevelItemCount()):
392 itm = self.packagesList.topLevelItem(index) 387 itm = self.packagesList.topLevelItem(index)
393 if itm.text(PipPackagesWidget.AvailableVersionColumn): 388 if itm.text(PipPackagesWidget.AvailableVersionColumn):
394 updateableItems.append(itm) 389 updateableItems.append(itm)
395 390
396 return updateableItems 391 return updateableItems
397 392
398 def __updateActionButtons(self): 393 def __updateActionButtons(self):
399 """ 394 """
400 Private method to set the state of the action buttons. 395 Private method to set the state of the action buttons.
401 """ 396 """
402 if self.__isPipAvailable(): 397 if self.__isPipAvailable():
403 self.upgradeButton.setEnabled( 398 self.upgradeButton.setEnabled(bool(self.__selectedUpdateableItems()))
404 bool(self.__selectedUpdateableItems())) 399 self.uninstallButton.setEnabled(bool(self.packagesList.selectedItems()))
405 self.uninstallButton.setEnabled( 400 self.upgradeAllButton.setEnabled(bool(self.__allUpdateableItems()))
406 bool(self.packagesList.selectedItems()))
407 self.upgradeAllButton.setEnabled(
408 bool(self.__allUpdateableItems()))
409 self.showPackageDetailsButton.setEnabled( 401 self.showPackageDetailsButton.setEnabled(
410 len(self.packagesList.selectedItems()) == 1) 402 len(self.packagesList.selectedItems()) == 1
403 )
411 else: 404 else:
412 self.upgradeButton.setEnabled(False) 405 self.upgradeButton.setEnabled(False)
413 self.uninstallButton.setEnabled(False) 406 self.uninstallButton.setEnabled(False)
414 self.upgradeAllButton.setEnabled(False) 407 self.upgradeAllButton.setEnabled(False)
415 self.showPackageDetailsButton.setEnabled(False) 408 self.showPackageDetailsButton.setEnabled(False)
416 409
417 def __refreshPackagesList(self): 410 def __refreshPackagesList(self):
418 """ 411 """
419 Private method to refresh the packages list. 412 Private method to refresh the packages list.
420 """ 413 """
421 self.packagesList.clear() 414 self.packagesList.clear()
422 venvName = self.environmentsComboBox.currentText() 415 venvName = self.environmentsComboBox.currentText()
423 if venvName: 416 if venvName:
424 interpreter = self.__pip.getVirtualenvInterpreter(venvName) 417 interpreter = self.__pip.getVirtualenvInterpreter(venvName)
425 if interpreter: 418 if interpreter:
426 self.statusLabel.show() 419 self.statusLabel.show()
427 self.statusLabel.setText( 420 self.statusLabel.setText(self.tr("Getting installed packages..."))
428 self.tr("Getting installed packages...")) 421
429
430 with EricOverrideCursor(): 422 with EricOverrideCursor():
431 # 1. populate with installed packages 423 # 1. populate with installed packages
432 self.packagesList.setUpdatesEnabled(False) 424 self.packagesList.setUpdatesEnabled(False)
433 installedPackages = self.__pip.getInstalledPackages( 425 installedPackages = self.__pip.getInstalledPackages(
434 venvName, 426 venvName,
435 localPackages=self.localCheckBox.isChecked(), 427 localPackages=self.localCheckBox.isChecked(),
436 notRequired=self.notRequiredCheckBox.isChecked(), 428 notRequired=self.notRequiredCheckBox.isChecked(),
437 usersite=self.userCheckBox.isChecked(), 429 usersite=self.userCheckBox.isChecked(),
438 ) 430 )
439 for package, version in installedPackages: 431 for package, version in installedPackages:
440 QTreeWidgetItem(self.packagesList, 432 QTreeWidgetItem(self.packagesList, [package, version, "", ""])
441 [package, version, "", ""])
442 self.packagesList.setUpdatesEnabled(True) 433 self.packagesList.setUpdatesEnabled(True)
443 self.statusLabel.setText( 434 self.statusLabel.setText(self.tr("Getting outdated packages..."))
444 self.tr("Getting outdated packages..."))
445 QApplication.processEvents() 435 QApplication.processEvents()
446 436
447 # 2. update with update information 437 # 2. update with update information
448 self.packagesList.setUpdatesEnabled(False) 438 self.packagesList.setUpdatesEnabled(False)
449 outdatedPackages = self.__pip.getOutdatedPackages( 439 outdatedPackages = self.__pip.getOutdatedPackages(
450 venvName, 440 venvName,
451 localPackages=self.localCheckBox.isChecked(), 441 localPackages=self.localCheckBox.isChecked(),
453 usersite=self.userCheckBox.isChecked(), 443 usersite=self.userCheckBox.isChecked(),
454 ) 444 )
455 for package, _version, latest in outdatedPackages: 445 for package, _version, latest in outdatedPackages:
456 items = self.packagesList.findItems( 446 items = self.packagesList.findItems(
457 package, 447 package,
458 Qt.MatchFlag.MatchExactly | 448 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
459 Qt.MatchFlag.MatchCaseSensitive
460 ) 449 )
461 if items: 450 if items:
462 itm = items[0] 451 itm = items[0]
463 itm.setText( 452 itm.setText(
464 PipPackagesWidget.AvailableVersionColumn, 453 PipPackagesWidget.AvailableVersionColumn, latest
465 latest) 454 )
466 455
467 self.packagesList.sortItems( 456 self.packagesList.sortItems(
468 PipPackagesWidget.PackageColumn, 457 PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder
469 Qt.SortOrder.AscendingOrder) 458 )
470 for col in range(self.packagesList.columnCount()): 459 for col in range(self.packagesList.columnCount()):
471 self.packagesList.resizeColumnToContents(col) 460 self.packagesList.resizeColumnToContents(col)
472 self.packagesList.setUpdatesEnabled(True) 461 self.packagesList.setUpdatesEnabled(True)
473 462
474 # 3. update with vulnerability information 463 # 3. update with vulnerability information
475 if self.vulnerabilityCheckBox.isChecked(): 464 if self.vulnerabilityCheckBox.isChecked():
476 self.__updateVulnerabilityData() 465 self.__updateVulnerabilityData()
477 self.statusLabel.hide() 466 self.statusLabel.hide()
478 467
479 self.__updateActionButtons() 468 self.__updateActionButtons()
480 self.__updateSearchActionButtons() 469 self.__updateSearchActionButtons()
481 self.__updateSearchButton() 470 self.__updateSearchButton()
482 self.__updateSearchMoreButton(False) 471 self.__updateSearchMoreButton(False)
483 472
484 @pyqtSlot(str) 473 @pyqtSlot(str)
485 def on_environmentsComboBox_currentTextChanged(self, name): 474 def on_environmentsComboBox_currentTextChanged(self, name):
486 """ 475 """
487 Private slot handling the selection of a Python environment. 476 Private slot handling the selection of a Python environment.
488 477
489 @param name name of the selected Python environment 478 @param name name of the selected Python environment
490 @type str 479 @type str
491 """ 480 """
492 if name != self.__selectedEnvironment: 481 if name != self.__selectedEnvironment:
493 if self.viewToggleButton.isChecked(): 482 if self.viewToggleButton.isChecked():
494 self.__refreshDependencyTree() 483 self.__refreshDependencyTree()
495 else: 484 else:
496 self.__refreshPackagesList() 485 self.__refreshPackagesList()
497 self.__selectedEnvironment = name 486 self.__selectedEnvironment = name
498 487
499 @pyqtSlot() 488 @pyqtSlot()
500 def on_localCheckBox_clicked(self): 489 def on_localCheckBox_clicked(self):
501 """ 490 """
502 Private slot handling the switching of the local mode. 491 Private slot handling the switching of the local mode.
503 """ 492 """
504 self.__refreshPackagesList() 493 self.__refreshPackagesList()
505 494
506 @pyqtSlot() 495 @pyqtSlot()
507 def on_notRequiredCheckBox_clicked(self): 496 def on_notRequiredCheckBox_clicked(self):
508 """ 497 """
509 Private slot handling the switching of the 'not required' mode. 498 Private slot handling the switching of the 'not required' mode.
510 """ 499 """
511 self.__refreshPackagesList() 500 self.__refreshPackagesList()
512 501
513 @pyqtSlot() 502 @pyqtSlot()
514 def on_userCheckBox_clicked(self): 503 def on_userCheckBox_clicked(self):
515 """ 504 """
516 Private slot handling the switching of the 'user-site' mode. 505 Private slot handling the switching of the 'user-site' mode.
517 """ 506 """
518 self.__refreshPackagesList() 507 self.__refreshPackagesList()
519 508
520 def __showPackageInformation(self, packageName, infoWidget): 509 def __showPackageInformation(self, packageName, infoWidget):
521 """ 510 """
522 Private method to show information for a package. 511 Private method to show information for a package.
523 512
524 @param packageName name of the package 513 @param packageName name of the package
525 @type str 514 @type str
526 @param infoWidget reference to the widget to contain the information 515 @param infoWidget reference to the widget to contain the information
527 @type QTreeWidget 516 @type QTreeWidget
528 """ 517 """
529 environment = self.environmentsComboBox.currentText() 518 environment = self.environmentsComboBox.currentText()
530 interpreter = self.__pip.getVirtualenvInterpreter(environment) 519 interpreter = self.__pip.getVirtualenvInterpreter(environment)
531 if not interpreter: 520 if not interpreter:
532 return 521 return
533 522
534 args = ["-m", "pip", "show"] 523 args = ["-m", "pip", "show"]
535 if self.verboseCheckBox.isChecked(): 524 if self.verboseCheckBox.isChecked():
536 args.append("--verbose") 525 args.append("--verbose")
537 if self.installedFilesCheckBox.isChecked(): 526 if self.installedFilesCheckBox.isChecked():
538 args.append("--files") 527 args.append("--files")
539 args.append(packageName) 528 args.append(packageName)
540 529
541 with EricOverrideCursor(): 530 with EricOverrideCursor():
542 success, output = self.__pip.runProcess(args, interpreter) 531 success, output = self.__pip.runProcess(args, interpreter)
543 532
544 if success and output: 533 if success and output:
545 mode = self.ShowProcessGeneralMode 534 mode = self.ShowProcessGeneralMode
546 for line in output.splitlines(): 535 for line in output.splitlines():
547 line = line.rstrip() 536 line = line.rstrip()
548 if line != "---": 537 if line != "---":
549 if mode != self.ShowProcessGeneralMode: 538 if mode != self.ShowProcessGeneralMode:
550 if line[0] == " ": 539 if line[0] == " ":
551 QTreeWidgetItem( 540 QTreeWidgetItem(infoWidget, [" ", line.strip()])
552 infoWidget,
553 [" ", line.strip()])
554 else: 541 else:
555 mode = self.ShowProcessGeneralMode 542 mode = self.ShowProcessGeneralMode
556 if mode == self.ShowProcessGeneralMode: 543 if mode == self.ShowProcessGeneralMode:
557 try: 544 try:
558 label, info = line.split(": ", 1) 545 label, info = line.split(": ", 1)
560 label = line[:-1] 547 label = line[:-1]
561 info = "" 548 info = ""
562 label = label.lower() 549 label = label.lower()
563 if label in self.__infoLabels: 550 if label in self.__infoLabels:
564 QTreeWidgetItem( 551 QTreeWidgetItem(
565 infoWidget, 552 infoWidget, [self.__infoLabels[label], info]
566 [self.__infoLabels[label], info]) 553 )
567 if label == "files": 554 if label == "files":
568 mode = self.ShowProcessFilesListMode 555 mode = self.ShowProcessFilesListMode
569 elif label == "classifiers": 556 elif label == "classifiers":
570 mode = self.ShowProcessClassifiersMode 557 mode = self.ShowProcessClassifiersMode
571 elif label == "entry-points": 558 elif label == "entry-points":
572 mode = self.ShowProcessEntryPointsMode 559 mode = self.ShowProcessEntryPointsMode
573 infoWidget.scrollToTop() 560 infoWidget.scrollToTop()
574 561
575 header = infoWidget.header() 562 header = infoWidget.header()
576 header.setStretchLastSection(False) 563 header.setStretchLastSection(False)
577 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) 564 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents)
578 if ( 565 if header.sectionSize(0) + header.sectionSize(1) < header.width():
579 header.sectionSize(0) + header.sectionSize(1) <
580 header.width()
581 ):
582 header.setStretchLastSection(True) 566 header.setStretchLastSection(True)
583 567
584 @pyqtSlot() 568 @pyqtSlot()
585 def on_packagesList_itemSelectionChanged(self): 569 def on_packagesList_itemSelectionChanged(self):
586 """ 570 """
587 Private slot reacting on a change of selected items. 571 Private slot reacting on a change of selected items.
588 """ 572 """
589 if len(self.packagesList.selectedItems()) == 0: 573 if len(self.packagesList.selectedItems()) == 0:
590 self.infoWidget.clear() 574 self.infoWidget.clear()
591 575
592 @pyqtSlot(QTreeWidgetItem, int) 576 @pyqtSlot(QTreeWidgetItem, int)
593 def on_packagesList_itemPressed(self, item, column): 577 def on_packagesList_itemPressed(self, item, column):
594 """ 578 """
595 Private slot reacting on a package item being pressed. 579 Private slot reacting on a package item being pressed.
596 580
597 @param item reference to the pressed item 581 @param item reference to the pressed item
598 @type QTreeWidgetItem 582 @type QTreeWidgetItem
599 @param column pressed column 583 @param column pressed column
600 @type int 584 @type int
601 """ 585 """
602 self.infoWidget.clear() 586 self.infoWidget.clear()
603 587
604 if item is not None: 588 if item is not None:
605 if ( 589 if column == PipPackagesWidget.VulnerabilityColumn and bool(
606 column == PipPackagesWidget.VulnerabilityColumn and 590 item.text(PipPackagesWidget.VulnerabilityColumn)
607 bool(item.text(PipPackagesWidget.VulnerabilityColumn))
608 ): 591 ):
609 self.__showVulnerabilityInformation( 592 self.__showVulnerabilityInformation(
610 item.text(PipPackagesWidget.PackageColumn), 593 item.text(PipPackagesWidget.PackageColumn),
611 item.text(PipPackagesWidget.InstalledVersionColumn), 594 item.text(PipPackagesWidget.InstalledVersionColumn),
612 item.data(PipPackagesWidget.VulnerabilityColumn, 595 item.data(
613 PipPackagesWidget.VulnerabilityRole) 596 PipPackagesWidget.VulnerabilityColumn,
597 PipPackagesWidget.VulnerabilityRole,
598 ),
614 ) 599 )
615 else: 600 else:
616 self.__showPackageInformation( 601 self.__showPackageInformation(
617 item.text(PipPackagesWidget.PackageColumn), 602 item.text(PipPackagesWidget.PackageColumn), self.infoWidget
618 self.infoWidget
619 ) 603 )
620 604
621 self.__updateActionButtons() 605 self.__updateActionButtons()
622 606
623 @pyqtSlot(QTreeWidgetItem, int) 607 @pyqtSlot(QTreeWidgetItem, int)
624 def on_packagesList_itemActivated(self, item, column): 608 def on_packagesList_itemActivated(self, item, column):
625 """ 609 """
626 Private slot reacting on a package item being activated. 610 Private slot reacting on a package item being activated.
627 611
628 @param item reference to the activated item 612 @param item reference to the activated item
629 @type QTreeWidgetItem 613 @type QTreeWidgetItem
630 @param column activated column 614 @param column activated column
631 @type int 615 @type int
632 """ 616 """
633 packageName = item.text(PipPackagesWidget.PackageColumn) 617 packageName = item.text(PipPackagesWidget.PackageColumn)
634 upgradable = bool(item.text(PipPackagesWidget.AvailableVersionColumn)) 618 upgradable = bool(item.text(PipPackagesWidget.AvailableVersionColumn))
635 if column == PipPackagesWidget.InstalledVersionColumn: 619 if column == PipPackagesWidget.InstalledVersionColumn:
636 # show details for installed version 620 # show details for installed version
637 packageVersion = item.text( 621 packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn)
638 PipPackagesWidget.InstalledVersionColumn)
639 else: 622 else:
640 # show details for available version or installed one 623 # show details for available version or installed one
641 if item.text(PipPackagesWidget.AvailableVersionColumn): 624 if item.text(PipPackagesWidget.AvailableVersionColumn):
642 packageVersion = item.text( 625 packageVersion = item.text(PipPackagesWidget.AvailableVersionColumn)
643 PipPackagesWidget.AvailableVersionColumn)
644 else: 626 else:
645 packageVersion = item.text( 627 packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn)
646 PipPackagesWidget.InstalledVersionColumn) 628
647 629 self.__showPackageDetails(packageName, packageVersion, upgradable=upgradable)
648 self.__showPackageDetails(packageName, packageVersion, 630
649 upgradable=upgradable)
650
651 @pyqtSlot(bool) 631 @pyqtSlot(bool)
652 def on_verboseCheckBox_clicked(self, checked): 632 def on_verboseCheckBox_clicked(self, checked):
653 """ 633 """
654 Private slot to handle a change of the verbose package information 634 Private slot to handle a change of the verbose package information
655 checkbox. 635 checkbox.
656 636
657 @param checked state of the checkbox 637 @param checked state of the checkbox
658 @type bool 638 @type bool
659 """ 639 """
660 self.on_packagesList_itemPressed(self.packagesList.currentItem(), 640 self.on_packagesList_itemPressed(
661 self.packagesList.currentColumn()) 641 self.packagesList.currentItem(), self.packagesList.currentColumn()
662 642 )
643
663 @pyqtSlot(bool) 644 @pyqtSlot(bool)
664 def on_installedFilesCheckBox_clicked(self, checked): 645 def on_installedFilesCheckBox_clicked(self, checked):
665 """ 646 """
666 Private slot to handle a change of the installed files information 647 Private slot to handle a change of the installed files information
667 checkbox. 648 checkbox.
668 649
669 @param checked state of the checkbox 650 @param checked state of the checkbox
670 @type bool 651 @type bool
671 """ 652 """
672 self.on_packagesList_itemPressed(self.packagesList.currentItem(), 653 self.on_packagesList_itemPressed(
673 self.packagesList.currentColumn()) 654 self.packagesList.currentItem(), self.packagesList.currentColumn()
674 655 )
656
675 @pyqtSlot() 657 @pyqtSlot()
676 def on_refreshButton_clicked(self): 658 def on_refreshButton_clicked(self):
677 """ 659 """
678 Private slot to refresh the display. 660 Private slot to refresh the display.
679 """ 661 """
680 currentEnvironment = self.environmentsComboBox.currentText() 662 currentEnvironment = self.environmentsComboBox.currentText()
681 self.environmentsComboBox.clear() 663 self.environmentsComboBox.clear()
682 self.packagesList.clear() 664 self.packagesList.clear()
683 665
684 with EricOverrideCursor(): 666 with EricOverrideCursor():
685 self.__populateEnvironments() 667 self.__populateEnvironments()
686 668
687 index = self.environmentsComboBox.findText( 669 index = self.environmentsComboBox.findText(
688 currentEnvironment, 670 currentEnvironment,
689 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive 671 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
690 ) 672 )
691 if index != -1: 673 if index != -1:
692 self.environmentsComboBox.setCurrentIndex(index) 674 self.environmentsComboBox.setCurrentIndex(index)
693 675
694 self.__updateActionButtons() 676 self.__updateActionButtons()
695 677
696 @pyqtSlot() 678 @pyqtSlot()
697 def on_upgradeButton_clicked(self): 679 def on_upgradeButton_clicked(self):
698 """ 680 """
699 Private slot to upgrade selected packages of the selected environment. 681 Private slot to upgrade selected packages of the selected environment.
700 """ 682 """
701 packages = [itm.text(PipPackagesWidget.PackageColumn) 683 packages = [
702 for itm in self.__selectedUpdateableItems()] 684 itm.text(PipPackagesWidget.PackageColumn)
685 for itm in self.__selectedUpdateableItems()
686 ]
703 if packages: 687 if packages:
704 self.executeUpgradePackages(packages) 688 self.executeUpgradePackages(packages)
705 689
706 @pyqtSlot() 690 @pyqtSlot()
707 def on_upgradeAllButton_clicked(self): 691 def on_upgradeAllButton_clicked(self):
708 """ 692 """
709 Private slot to upgrade all packages of the selected environment. 693 Private slot to upgrade all packages of the selected environment.
710 """ 694 """
711 packages = [itm.text(PipPackagesWidget.PackageColumn) 695 packages = [
712 for itm in self.__allUpdateableItems()] 696 itm.text(PipPackagesWidget.PackageColumn)
697 for itm in self.__allUpdateableItems()
698 ]
713 if packages: 699 if packages:
714 self.executeUpgradePackages(packages) 700 self.executeUpgradePackages(packages)
715 701
716 @pyqtSlot() 702 @pyqtSlot()
717 def on_uninstallButton_clicked(self): 703 def on_uninstallButton_clicked(self):
718 """ 704 """
719 Private slot to remove selected packages of the selected environment. 705 Private slot to remove selected packages of the selected environment.
720 """ 706 """
721 packages = [itm.text(PipPackagesWidget.PackageColumn) 707 packages = [
722 for itm in self.packagesList.selectedItems()] 708 itm.text(PipPackagesWidget.PackageColumn)
709 for itm in self.packagesList.selectedItems()
710 ]
723 self.executeUninstallPackages(packages) 711 self.executeUninstallPackages(packages)
724 712
725 def executeUninstallPackages(self, packages): 713 def executeUninstallPackages(self, packages):
726 """ 714 """
727 Public method to uninstall the given list of packages. 715 Public method to uninstall the given list of packages.
728 716
729 @param packages list of package names to be uninstalled 717 @param packages list of package names to be uninstalled
730 @type list of str 718 @type list of str
731 """ 719 """
732 if packages: 720 if packages:
733 ok = self.__pip.uninstallPackages( 721 ok = self.__pip.uninstallPackages(
734 packages, 722 packages, venvName=self.environmentsComboBox.currentText()
735 venvName=self.environmentsComboBox.currentText()) 723 )
736 if ok: 724 if ok:
737 self.on_refreshButton_clicked() 725 self.on_refreshButton_clicked()
738 726
739 def executeUpgradePackages(self, packages): 727 def executeUpgradePackages(self, packages):
740 """ 728 """
741 Public method to execute the pip upgrade command. 729 Public method to execute the pip upgrade command.
742 730
743 @param packages list of package names to be upgraded 731 @param packages list of package names to be upgraded
744 @type list of str 732 @type list of str
745 """ 733 """
746 ok = self.__pip.upgradePackages( 734 ok = self.__pip.upgradePackages(
747 packages, venvName=self.environmentsComboBox.currentText(), 735 packages,
748 userSite=self.userCheckBox.isChecked()) 736 venvName=self.environmentsComboBox.currentText(),
737 userSite=self.userCheckBox.isChecked(),
738 )
749 if ok: 739 if ok:
750 self.on_refreshButton_clicked() 740 self.on_refreshButton_clicked()
751 741
752 @pyqtSlot() 742 @pyqtSlot()
753 def on_showPackageDetailsButton_clicked(self): 743 def on_showPackageDetailsButton_clicked(self):
754 """ 744 """
755 Private slot to show information for the selected package. 745 Private slot to show information for the selected package.
756 """ 746 """
757 item = self.packagesList.selectedItems()[0] 747 item = self.packagesList.selectedItems()[0]
758 if item: 748 if item:
759 packageName = item.text(PipPackagesWidget.PackageColumn) 749 packageName = item.text(PipPackagesWidget.PackageColumn)
760 upgradable = bool(item.text( 750 upgradable = bool(item.text(PipPackagesWidget.AvailableVersionColumn))
761 PipPackagesWidget.AvailableVersionColumn))
762 # show details for available version or installed one 751 # show details for available version or installed one
763 if item.text(PipPackagesWidget.AvailableVersionColumn): 752 if item.text(PipPackagesWidget.AvailableVersionColumn):
764 packageVersion = item.text( 753 packageVersion = item.text(PipPackagesWidget.AvailableVersionColumn)
765 PipPackagesWidget.AvailableVersionColumn)
766 else: 754 else:
767 packageVersion = item.text( 755 packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn)
768 PipPackagesWidget.InstalledVersionColumn) 756
769 757 self.__showPackageDetails(
770 self.__showPackageDetails(packageName, packageVersion, 758 packageName, packageVersion, upgradable=upgradable
771 upgradable=upgradable) 759 )
772 760
773 ####################################################################### 761 #######################################################################
774 ## Search widget related methods below 762 ## Search widget related methods below
775 ####################################################################### 763 #######################################################################
776 764
777 def __updateSearchActionButtons(self): 765 def __updateSearchActionButtons(self):
778 """ 766 """
779 Private method to update the action button states of the search widget. 767 Private method to update the action button states of the search widget.
780 """ 768 """
781 installEnable = ( 769 installEnable = (
782 len(self.searchResultList.selectedItems()) > 0 and 770 len(self.searchResultList.selectedItems()) > 0
783 self.environmentsComboBox.currentIndex() > 0 and 771 and self.environmentsComboBox.currentIndex() > 0
784 self.__isPipAvailable() 772 and self.__isPipAvailable()
785 ) 773 )
786 self.installButton.setEnabled(installEnable) 774 self.installButton.setEnabled(installEnable)
787 self.installUserSiteButton.setEnabled(installEnable) 775 self.installUserSiteButton.setEnabled(installEnable)
788 776
789 self.showDetailsButton.setEnabled( 777 self.showDetailsButton.setEnabled(
790 len(self.searchResultList.selectedItems()) == 1 and 778 len(self.searchResultList.selectedItems()) == 1 and self.__isPipAvailable()
791 self.__isPipAvailable() 779 )
792 ) 780
793
794 def __updateSearchButton(self): 781 def __updateSearchButton(self):
795 """ 782 """
796 Private method to update the state of the search button. 783 Private method to update the state of the search button.
797 """ 784 """
798 self.searchButton.setEnabled( 785 self.searchButton.setEnabled(
799 bool(self.searchEditName.text()) and 786 bool(self.searchEditName.text()) and self.__isPipAvailable()
800 self.__isPipAvailable() 787 )
801 ) 788
802
803 def __updateSearchMoreButton(self, enable): 789 def __updateSearchMoreButton(self, enable):
804 """ 790 """
805 Private method to update the state of the search more button. 791 Private method to update the state of the search more button.
806 792
807 @param enable flag indicating the desired enable state 793 @param enable flag indicating the desired enable state
808 @type bool 794 @type bool
809 """ 795 """
810 self.searchMoreButton.setEnabled( 796 self.searchMoreButton.setEnabled(
811 enable and 797 enable and bool(self.searchEditName.text()) and self.__isPipAvailable()
812 bool(self.searchEditName.text()) and 798 )
813 self.__isPipAvailable() 799
814 )
815
816 @pyqtSlot(bool) 800 @pyqtSlot(bool)
817 def on_searchToggleButton_toggled(self, checked): 801 def on_searchToggleButton_toggled(self, checked):
818 """ 802 """
819 Private slot to togle the search widget. 803 Private slot to togle the search widget.
820 804
821 @param checked state of the search widget button 805 @param checked state of the search widget button
822 @type bool 806 @type bool
823 """ 807 """
824 self.searchWidget.setVisible(checked) 808 self.searchWidget.setVisible(checked)
825 809
826 if checked: 810 if checked:
827 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) 811 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason)
828 self.searchEditName.selectAll() 812 self.searchEditName.selectAll()
829 813
830 self.__updateSearchActionButtons() 814 self.__updateSearchActionButtons()
831 self.__updateSearchButton() 815 self.__updateSearchButton()
832 self.__updateSearchMoreButton(False) 816 self.__updateSearchMoreButton(False)
833 817
834 @pyqtSlot(str) 818 @pyqtSlot(str)
835 def on_searchEditName_textChanged(self, txt): 819 def on_searchEditName_textChanged(self, txt):
836 """ 820 """
837 Private slot handling a change of the search term. 821 Private slot handling a change of the search term.
838 822
839 @param txt search term 823 @param txt search term
840 @type str 824 @type str
841 """ 825 """
842 self.__updateSearchButton() 826 self.__updateSearchButton()
843 827
844 @pyqtSlot() 828 @pyqtSlot()
845 def on_searchEditName_returnPressed(self): 829 def on_searchEditName_returnPressed(self):
846 """ 830 """
847 Private slot initiating a search via a press of the Return key. 831 Private slot initiating a search via a press of the Return key.
848 """ 832 """
849 if ( 833 if bool(self.searchEditName.text()) and self.__isPipAvailable():
850 bool(self.searchEditName.text()) and
851 self.__isPipAvailable()
852 ):
853 self.__searchFirst() 834 self.__searchFirst()
854 835
855 @pyqtSlot() 836 @pyqtSlot()
856 def on_searchButton_clicked(self): 837 def on_searchButton_clicked(self):
857 """ 838 """
858 Private slot handling a press of the search button. 839 Private slot handling a press of the search button.
859 """ 840 """
860 self.__searchFirst() 841 self.__searchFirst()
861 842
862 @pyqtSlot() 843 @pyqtSlot()
863 def on_searchMoreButton_clicked(self): 844 def on_searchMoreButton_clicked(self):
864 """ 845 """
865 Private slot handling a press of the search more button. 846 Private slot handling a press of the search more button.
866 """ 847 """
867 self.__search(self.__lastSearchPage + 1) 848 self.__search(self.__lastSearchPage + 1)
868 849
869 @pyqtSlot() 850 @pyqtSlot()
870 def on_searchResultList_itemSelectionChanged(self): 851 def on_searchResultList_itemSelectionChanged(self):
871 """ 852 """
872 Private slot handling changes of the search result selection. 853 Private slot handling changes of the search result selection.
873 """ 854 """
874 self.__updateSearchActionButtons() 855 self.__updateSearchActionButtons()
875 856
876 def __searchFirst(self): 857 def __searchFirst(self):
877 """ 858 """
878 Private method to perform the search for packages. 859 Private method to perform the search for packages.
879 """ 860 """
880 self.searchResultList.clear() 861 self.searchResultList.clear()
881 self.searchInfoLabel.clear() 862 self.searchInfoLabel.clear()
882 863
883 self.__updateSearchMoreButton(False) 864 self.__updateSearchMoreButton(False)
884 865
885 self.__search() 866 self.__search()
886 867
887 def __search(self, page=1): 868 def __search(self, page=1):
888 """ 869 """
889 Private method to perform the search by calling the PyPI search URL. 870 Private method to perform the search by calling the PyPI search URL.
890 871
891 @param page search page to retrieve (defaults to 1) 872 @param page search page to retrieve (defaults to 1)
892 @type int (optional) 873 @type int (optional)
893 """ 874 """
894 self.__lastSearchPage = page 875 self.__lastSearchPage = page
895 876
896 self.searchButton.setEnabled(False) 877 self.searchButton.setEnabled(False)
897 878
898 searchTerm = self.searchEditName.text().strip() 879 searchTerm = self.searchEditName.text().strip()
899 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode() 880 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode()
900 urlQuery = QUrlQuery() 881 urlQuery = QUrlQuery()
901 urlQuery.addQueryItem("q", searchTerm) 882 urlQuery.addQueryItem("q", searchTerm)
902 urlQuery.addQueryItem("page", str(page)) 883 urlQuery.addQueryItem("page", str(page))
903 url = QUrl(self.__pip.getIndexUrlSearch()) 884 url = QUrl(self.__pip.getIndexUrlSearch())
904 url.setQuery(urlQuery) 885 url.setQuery(urlQuery)
905 886
906 request = QNetworkRequest(QUrl(url)) 887 request = QNetworkRequest(QUrl(url))
907 request.setAttribute( 888 request.setAttribute(
908 QNetworkRequest.Attribute.CacheLoadControlAttribute, 889 QNetworkRequest.Attribute.CacheLoadControlAttribute,
909 QNetworkRequest.CacheLoadControl.AlwaysNetwork) 890 QNetworkRequest.CacheLoadControl.AlwaysNetwork,
891 )
910 reply = self.__pip.getNetworkAccessManager().get(request) 892 reply = self.__pip.getNetworkAccessManager().get(request)
911 reply.finished.connect( 893 reply.finished.connect(lambda: self.__searchResponse(reply))
912 lambda: self.__searchResponse(reply))
913 self.__replies.append(reply) 894 self.__replies.append(reply)
914 895
915 def __searchResponse(self, reply): 896 def __searchResponse(self, reply):
916 """ 897 """
917 Private method to extract the search result data from the response. 898 Private method to extract the search result data from the response.
918 899
919 @param reply reference to the reply object containing the data 900 @param reply reference to the reply object containing the data
920 @type QNetworkReply 901 @type QNetworkReply
921 """ 902 """
922 if reply in self.__replies: 903 if reply in self.__replies:
923 self.__replies.remove(reply) 904 self.__replies.remove(reply)
924 905
925 urlQuery = QUrlQuery(reply.url()) 906 urlQuery = QUrlQuery(reply.url())
926 searchTerm = urlQuery.queryItemValue("q") 907 searchTerm = urlQuery.queryItemValue("q")
927 908
928 if reply.error() != QNetworkReply.NetworkError.NoError: 909 if reply.error() != QNetworkReply.NetworkError.NoError:
929 EricMessageBox.warning( 910 EricMessageBox.warning(
930 None, 911 None,
931 self.tr("Search PyPI"), 912 self.tr("Search PyPI"),
932 self.tr( 913 self.tr(
933 "<p>Received an error while searching for <b>{0}</b>.</p>" 914 "<p>Received an error while searching for <b>{0}</b>.</p>"
934 "<p>Error: {1}</p>" 915 "<p>Error: {1}</p>"
935 ).format(searchTerm, reply.errorString()) 916 ).format(searchTerm, reply.errorString()),
936 ) 917 )
937 reply.deleteLater() 918 reply.deleteLater()
938 return 919 return
939 920
940 data = bytes(reply.readAll()).decode() 921 data = bytes(reply.readAll()).decode()
941 reply.deleteLater() 922 reply.deleteLater()
942 923
943 results = PypiSearchResultsParser(data).getResults() 924 results = PypiSearchResultsParser(data).getResults()
944 if results: 925 if results:
945 # PyPI returns max. 20 entries per page 926 # PyPI returns max. 20 entries per page
946 if len(results) < 20: 927 if len(results) < 20:
947 msg = self.tr("%n package(s) found.", "", 928 msg = self.tr(
948 (self.__lastSearchPage - 1) * 20 + len(results)) 929 "%n package(s) found.",
930 "",
931 (self.__lastSearchPage - 1) * 20 + len(results),
932 )
949 self.__updateSearchMoreButton(False) 933 self.__updateSearchMoreButton(False)
950 else: 934 else:
951 msg = self.tr("Showing first {0} packages found.").format( 935 msg = self.tr("Showing first {0} packages found.").format(
952 self.__lastSearchPage * 20) 936 self.__lastSearchPage * 20
937 )
953 self.__updateSearchMoreButton(True) 938 self.__updateSearchMoreButton(True)
954 self.searchInfoLabel.setText(msg) 939 self.searchInfoLabel.setText(msg)
955 lastItem = self.searchResultList.topLevelItem( 940 lastItem = self.searchResultList.topLevelItem(
956 self.searchResultList.topLevelItemCount() - 1) 941 self.searchResultList.topLevelItemCount() - 1
942 )
957 else: 943 else:
958 self.__updateSearchMoreButton(False) 944 self.__updateSearchMoreButton(False)
959 if self.__lastSearchPage == 1: 945 if self.__lastSearchPage == 1:
960 EricMessageBox.warning( 946 EricMessageBox.warning(
961 self, 947 self,
962 self.tr("Search PyPI"), 948 self.tr("Search PyPI"),
963 self.tr("""<p>There were no results for <b>{0}</b>.</p>""") 949 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format(
964 .format(searchTerm) 950 searchTerm
951 ),
965 ) 952 )
966 self.searchInfoLabel.setText( 953 self.searchInfoLabel.setText(
967 self.tr("""<p>There were no results for <b>{0}</b>.</p>""") 954 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format(
968 .format(searchTerm) 955 searchTerm
956 )
969 ) 957 )
970 else: 958 else:
971 EricMessageBox.warning( 959 EricMessageBox.warning(
972 self, 960 self,
973 self.tr("Search PyPI"), 961 self.tr("Search PyPI"),
974 self.tr("""<p>There were no more results for""" 962 self.tr(
975 """ <b>{0}</b>.</p>""").format(searchTerm) 963 """<p>There were no more results for""" """ <b>{0}</b>.</p>"""
964 ).format(searchTerm),
976 ) 965 )
977 lastItem = None 966 lastItem = None
978 967
979 wrapper = textwrap.TextWrapper(width=80) 968 wrapper = textwrap.TextWrapper(width=80)
980 for result in results: 969 for result in results:
981 try: 970 try:
982 description = "\n".join([ 971 description = "\n".join(
983 wrapper.fill(line) for line in 972 [
984 result['description'].strip().splitlines() 973 wrapper.fill(line)
985 ]) 974 for line in result["description"].strip().splitlines()
975 ]
976 )
986 except KeyError: 977 except KeyError:
987 description = "" 978 description = ""
988 date = ( 979 date = result["released"] if "released" in result else result["created"]
989 result["released"]
990 if "released" in result else
991 result["created"]
992 )
993 itm = QTreeWidgetItem( 980 itm = QTreeWidgetItem(
994 self.searchResultList, [ 981 self.searchResultList,
995 result['name'].strip(), 982 [
996 result['version'], 983 result["name"].strip(),
984 result["version"],
997 date.strip(), 985 date.strip(),
998 description, 986 description,
999 ]) 987 ],
1000 itm.setData(0, self.SearchVersionRole, result['version']) 988 )
1001 989 itm.setData(0, self.SearchVersionRole, result["version"])
990
1002 if lastItem: 991 if lastItem:
1003 self.searchResultList.scrollToItem( 992 self.searchResultList.scrollToItem(
1004 lastItem, 993 lastItem, QAbstractItemView.ScrollHint.PositionAtTop
1005 QAbstractItemView.ScrollHint.PositionAtTop) 994 )
1006 995
1007 header = self.searchResultList.header() 996 header = self.searchResultList.header()
1008 header.setStretchLastSection(False) 997 header.setStretchLastSection(False)
1009 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) 998 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents)
1010 headerSize = 0 999 headerSize = 0
1011 for col in range(header.count()): 1000 for col in range(header.count()):
1012 headerSize += header.sectionSize(col) 1001 headerSize += header.sectionSize(col)
1013 if headerSize < header.width(): 1002 if headerSize < header.width():
1014 header.setStretchLastSection(True) 1003 header.setStretchLastSection(True)
1015 1004
1016 self.__finishSearch() 1005 self.__finishSearch()
1017 1006
1018 def __finishSearch(self): 1007 def __finishSearch(self):
1019 """ 1008 """
1020 Private slot performing the search finishing actions. 1009 Private slot performing the search finishing actions.
1021 """ 1010 """
1022 self.__updateSearchActionButtons() 1011 self.__updateSearchActionButtons()
1023 self.__updateSearchButton() 1012 self.__updateSearchButton()
1024 1013
1025 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) 1014 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason)
1026 1015
1027 @pyqtSlot() 1016 @pyqtSlot()
1028 def on_installButton_clicked(self): 1017 def on_installButton_clicked(self):
1029 """ 1018 """
1030 Private slot to handle pressing the Install button.. 1019 Private slot to handle pressing the Install button..
1031 """ 1020 """
1032 packages = [ 1021 packages = [
1033 itm.text(0).strip() 1022 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1034 for itm in self.searchResultList.selectedItems()
1035 ] 1023 ]
1036 self.executeInstallPackages(packages) 1024 self.executeInstallPackages(packages)
1037 1025
1038 @pyqtSlot() 1026 @pyqtSlot()
1039 def on_installUserSiteButton_clicked(self): 1027 def on_installUserSiteButton_clicked(self):
1040 """ 1028 """
1041 Private slot to handle pressing the Install to User-Site button.. 1029 Private slot to handle pressing the Install to User-Site button..
1042 """ 1030 """
1043 packages = [ 1031 packages = [
1044 itm.text(0).strip() 1032 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1045 for itm in self.searchResultList.selectedItems()
1046 ] 1033 ]
1047 self.executeInstallPackages(packages, userSite=True) 1034 self.executeInstallPackages(packages, userSite=True)
1048 1035
1049 def executeInstallPackages(self, packages, userSite=False): 1036 def executeInstallPackages(self, packages, userSite=False):
1050 """ 1037 """
1051 Public method to install the given list of packages. 1038 Public method to install the given list of packages.
1052 1039
1053 @param packages list of package names to be installed 1040 @param packages list of package names to be installed
1054 @type list of str 1041 @type list of str
1055 @param userSite flag indicating to install to the user directory 1042 @param userSite flag indicating to install to the user directory
1056 @type bool 1043 @type bool
1057 """ 1044 """
1058 venvName = self.environmentsComboBox.currentText() 1045 venvName = self.environmentsComboBox.currentText()
1059 if venvName and packages: 1046 if venvName and packages:
1060 self.__pip.installPackages(packages, venvName=venvName, 1047 self.__pip.installPackages(packages, venvName=venvName, userSite=userSite)
1061 userSite=userSite)
1062 self.on_refreshButton_clicked() 1048 self.on_refreshButton_clicked()
1063 1049
1064 @pyqtSlot() 1050 @pyqtSlot()
1065 def on_showDetailsButton_clicked(self): 1051 def on_showDetailsButton_clicked(self):
1066 """ 1052 """
1067 Private slot to handle pressing the Show Details button. 1053 Private slot to handle pressing the Show Details button.
1068 """ 1054 """
1069 self.__showSearchedDetails() 1055 self.__showSearchedDetails()
1070 1056
1071 @pyqtSlot(QTreeWidgetItem, int) 1057 @pyqtSlot(QTreeWidgetItem, int)
1072 def on_searchResultList_itemActivated(self, item, column): 1058 def on_searchResultList_itemActivated(self, item, column):
1073 """ 1059 """
1074 Private slot reacting on an search result item activation. 1060 Private slot reacting on an search result item activation.
1075 1061
1076 @param item reference to the activated item 1062 @param item reference to the activated item
1077 @type QTreeWidgetItem 1063 @type QTreeWidgetItem
1078 @param column activated column 1064 @param column activated column
1079 @type int 1065 @type int
1080 """ 1066 """
1081 self.__showSearchedDetails(item) 1067 self.__showSearchedDetails(item)
1082 1068
1083 def __showSearchedDetails(self, item=None): 1069 def __showSearchedDetails(self, item=None):
1084 """ 1070 """
1085 Private slot to show details about the selected search result package. 1071 Private slot to show details about the selected search result package.
1086 1072
1087 @param item reference to the search result item to show details for 1073 @param item reference to the search result item to show details for
1088 @type QTreeWidgetItem 1074 @type QTreeWidgetItem
1089 """ 1075 """
1090 self.showDetailsButton.setEnabled(False) 1076 self.showDetailsButton.setEnabled(False)
1091 1077
1092 if not item: 1078 if not item:
1093 item = self.searchResultList.selectedItems()[0] 1079 item = self.searchResultList.selectedItems()[0]
1094 1080
1095 packageVersion = item.data(0, self.SearchVersionRole) 1081 packageVersion = item.data(0, self.SearchVersionRole)
1096 packageName = item.text(0) 1082 packageName = item.text(0)
1097 1083
1098 self.__showPackageDetails(packageName, packageVersion, 1084 self.__showPackageDetails(packageName, packageVersion, installable=True)
1099 installable=True) 1085
1100 1086 def __showPackageDetails(
1101 def __showPackageDetails(self, packageName, packageVersion, 1087 self, packageName, packageVersion, upgradable=False, installable=False
1102 upgradable=False, installable=False): 1088 ):
1103 """ 1089 """
1104 Private method to populate the package details dialog. 1090 Private method to populate the package details dialog.
1105 1091
1106 @param packageName name of the package to show details for 1092 @param packageName name of the package to show details for
1107 @type str 1093 @type str
1108 @param packageVersion version of the package 1094 @param packageVersion version of the package
1109 @type str 1095 @type str
1110 @param upgradable flag indicating that the package may be upgraded 1096 @param upgradable flag indicating that the package may be upgraded
1113 @param installable flag indicating that the package may be installed 1099 @param installable flag indicating that the package may be installed
1114 (defaults to False) 1100 (defaults to False)
1115 @type bool (optional) 1101 @type bool (optional)
1116 """ 1102 """
1117 with EricOverrideCursor(): 1103 with EricOverrideCursor():
1118 packageData = self.__pip.getPackageDetails( 1104 packageData = self.__pip.getPackageDetails(packageName, packageVersion)
1119 packageName, packageVersion) 1105
1120
1121 if packageData: 1106 if packageData:
1122 from .PipPackageDetailsDialog import PipPackageDetailsDialog 1107 from .PipPackageDetailsDialog import PipPackageDetailsDialog
1123 1108
1124 self.showDetailsButton.setEnabled(True) 1109 self.showDetailsButton.setEnabled(True)
1125 1110
1126 if installable: 1111 if installable:
1127 buttonsMode = PipPackageDetailsDialog.ButtonInstall 1112 buttonsMode = PipPackageDetailsDialog.ButtonInstall
1128 elif upgradable: 1113 elif upgradable:
1129 buttonsMode = ( 1114 buttonsMode = (
1130 PipPackageDetailsDialog.ButtonRemove | 1115 PipPackageDetailsDialog.ButtonRemove
1131 PipPackageDetailsDialog.ButtonUpgrade 1116 | PipPackageDetailsDialog.ButtonUpgrade
1132 ) 1117 )
1133 else: 1118 else:
1134 buttonsMode = PipPackageDetailsDialog.ButtonRemove 1119 buttonsMode = PipPackageDetailsDialog.ButtonRemove
1135 1120
1136 if self.__packageDetailsDialog is not None: 1121 if self.__packageDetailsDialog is not None:
1137 self.__packageDetailsDialog.close() 1122 self.__packageDetailsDialog.close()
1138 1123
1139 self.__packageDetailsDialog = ( 1124 self.__packageDetailsDialog = PipPackageDetailsDialog(
1140 PipPackageDetailsDialog(packageData, buttonsMode=buttonsMode, 1125 packageData, buttonsMode=buttonsMode, parent=self
1141 parent=self)
1142 ) 1126 )
1143 self.__packageDetailsDialog.show() 1127 self.__packageDetailsDialog.show()
1144 else: 1128 else:
1145 EricMessageBox.warning( 1129 EricMessageBox.warning(
1146 self, 1130 self,
1147 self.tr("Search PyPI"), 1131 self.tr("Search PyPI"),
1148 self.tr("""<p>No package details info for <b>{0}</b>""" 1132 self.tr(
1149 """ available.</p>""").format(packageName)) 1133 """<p>No package details info for <b>{0}</b>"""
1150 1134 """ available.</p>"""
1135 ).format(packageName),
1136 )
1137
1151 ####################################################################### 1138 #######################################################################
1152 ## Menu related methods below 1139 ## Menu related methods below
1153 ####################################################################### 1140 #######################################################################
1154 1141
1155 def __initPipMenu(self): 1142 def __initPipMenu(self):
1156 """ 1143 """
1157 Private method to create the super menu and attach it to the super 1144 Private method to create the super menu and attach it to the super
1158 menu button. 1145 menu button.
1159 """ 1146 """
1160 self.__pipMenu = QMenu() 1147 self.__pipMenu = QMenu()
1161 self.__installPipAct = self.__pipMenu.addAction( 1148 self.__installPipAct = self.__pipMenu.addAction(
1162 self.tr("Install Pip"), 1149 self.tr("Install Pip"), self.__installPip
1163 self.__installPip) 1150 )
1164 self.__installPipUserAct = self.__pipMenu.addAction( 1151 self.__installPipUserAct = self.__pipMenu.addAction(
1165 self.tr("Install Pip to User-Site"), 1152 self.tr("Install Pip to User-Site"), self.__installPipUser
1166 self.__installPipUser) 1153 )
1167 self.__repairPipAct = self.__pipMenu.addAction( 1154 self.__repairPipAct = self.__pipMenu.addAction(
1168 self.tr("Repair Pip"), 1155 self.tr("Repair Pip"), self.__repairPip
1169 self.__repairPip) 1156 )
1170 self.__pipMenu.addSeparator() 1157 self.__pipMenu.addSeparator()
1171 self.__installPackagesAct = self.__pipMenu.addAction( 1158 self.__installPackagesAct = self.__pipMenu.addAction(
1172 self.tr("Install Packages"), 1159 self.tr("Install Packages"), self.__installPackages
1173 self.__installPackages) 1160 )
1174 self.__installLocalPackageAct = self.__pipMenu.addAction( 1161 self.__installLocalPackageAct = self.__pipMenu.addAction(
1175 self.tr("Install Local Package"), 1162 self.tr("Install Local Package"), self.__installLocalPackage
1176 self.__installLocalPackage) 1163 )
1177 self.__pipMenu.addSeparator() 1164 self.__pipMenu.addSeparator()
1178 self.__installRequirementsAct = self.__pipMenu.addAction( 1165 self.__installRequirementsAct = self.__pipMenu.addAction(
1179 self.tr("Install Requirements"), 1166 self.tr("Install Requirements"), self.__installRequirements
1180 self.__installRequirements) 1167 )
1181 self.__reinstallPackagesAct = self.__pipMenu.addAction( 1168 self.__reinstallPackagesAct = self.__pipMenu.addAction(
1182 self.tr("Re-Install Selected Packages"), 1169 self.tr("Re-Install Selected Packages"), self.__reinstallPackages
1183 self.__reinstallPackages) 1170 )
1184 self.__uninstallRequirementsAct = self.__pipMenu.addAction( 1171 self.__uninstallRequirementsAct = self.__pipMenu.addAction(
1185 self.tr("Uninstall Requirements"), 1172 self.tr("Uninstall Requirements"), self.__uninstallRequirements
1186 self.__uninstallRequirements) 1173 )
1187 self.__generateRequirementsAct = self.__pipMenu.addAction( 1174 self.__generateRequirementsAct = self.__pipMenu.addAction(
1188 self.tr("Generate Requirements..."), 1175 self.tr("Generate Requirements..."), self.__generateRequirements
1189 self.__generateRequirements) 1176 )
1190 self.__pipMenu.addSeparator() 1177 self.__pipMenu.addSeparator()
1191 self.__showLicensesDialogAct = self.__pipMenu.addAction( 1178 self.__showLicensesDialogAct = self.__pipMenu.addAction(
1192 self.tr("Show Licenses..."), 1179 self.tr("Show Licenses..."), self.__showLicensesDialog
1193 self.__showLicensesDialog) 1180 )
1194 self.__pipMenu.addSeparator() 1181 self.__pipMenu.addSeparator()
1195 self.__checkVulnerabilityAct = self.__pipMenu.addAction( 1182 self.__checkVulnerabilityAct = self.__pipMenu.addAction(
1196 self.tr("Check Vulnerabilities"), 1183 self.tr("Check Vulnerabilities"), self.__updateVulnerabilityData
1197 self.__updateVulnerabilityData) 1184 )
1198 # updateVulnerabilityDbAct 1185 # updateVulnerabilityDbAct
1199 self.__pipMenu.addAction( 1186 self.__pipMenu.addAction(
1200 self.tr("Update Vulnerability Database"), 1187 self.tr("Update Vulnerability Database"), self.__updateVulnerabilityDbCache
1201 self.__updateVulnerabilityDbCache) 1188 )
1202 self.__pipMenu.addSeparator() 1189 self.__pipMenu.addSeparator()
1203 self.__cyclonedxAct = self.__pipMenu.addAction( 1190 self.__cyclonedxAct = self.__pipMenu.addAction(
1204 self.tr("Create SBOM file"), 1191 self.tr("Create SBOM file"), self.__createSBOMFile
1205 self.__createSBOMFile) 1192 )
1206 self.__pipMenu.addSeparator() 1193 self.__pipMenu.addSeparator()
1207 self.__cacheInfoAct = self.__pipMenu.addAction( 1194 self.__cacheInfoAct = self.__pipMenu.addAction(
1208 self.tr("Show Cache Info..."), 1195 self.tr("Show Cache Info..."), self.__showCacheInfo
1209 self.__showCacheInfo) 1196 )
1210 self.__cacheShowListAct = self.__pipMenu.addAction( 1197 self.__cacheShowListAct = self.__pipMenu.addAction(
1211 self.tr("Show Cached Files..."), 1198 self.tr("Show Cached Files..."), self.__showCacheList
1212 self.__showCacheList) 1199 )
1213 self.__cacheRemoveAct = self.__pipMenu.addAction( 1200 self.__cacheRemoveAct = self.__pipMenu.addAction(
1214 self.tr("Remove Cached Files..."), 1201 self.tr("Remove Cached Files..."), self.__removeCachedFiles
1215 self.__removeCachedFiles) 1202 )
1216 self.__cachePurgeAct = self.__pipMenu.addAction( 1203 self.__cachePurgeAct = self.__pipMenu.addAction(
1217 self.tr("Purge Cache..."), 1204 self.tr("Purge Cache..."), self.__purgeCache
1218 self.__purgeCache) 1205 )
1219 self.__pipMenu.addSeparator() 1206 self.__pipMenu.addSeparator()
1220 # editUserConfigAct 1207 # editUserConfigAct
1221 self.__pipMenu.addAction( 1208 self.__pipMenu.addAction(
1222 self.tr("Edit User Configuration..."), 1209 self.tr("Edit User Configuration..."), self.__editUserConfiguration
1223 self.__editUserConfiguration) 1210 )
1224 self.__editVirtualenvConfigAct = self.__pipMenu.addAction( 1211 self.__editVirtualenvConfigAct = self.__pipMenu.addAction(
1225 self.tr("Edit Environment Configuration..."), 1212 self.tr("Edit Environment Configuration..."),
1226 self.__editVirtualenvConfiguration) 1213 self.__editVirtualenvConfiguration,
1214 )
1227 self.__pipMenu.addSeparator() 1215 self.__pipMenu.addSeparator()
1228 # pipConfigAct 1216 # pipConfigAct
1229 self.__pipMenu.addAction( 1217 self.__pipMenu.addAction(self.tr("Configure..."), self.__pipConfigure)
1230 self.tr("Configure..."),
1231 self.__pipConfigure)
1232 1218
1233 self.__pipMenu.aboutToShow.connect(self.__aboutToShowPipMenu) 1219 self.__pipMenu.aboutToShow.connect(self.__aboutToShowPipMenu)
1234 1220
1235 self.pipMenuButton.setMenu(self.__pipMenu) 1221 self.pipMenuButton.setMenu(self.__pipMenu)
1236 1222
1237 def __aboutToShowPipMenu(self): 1223 def __aboutToShowPipMenu(self):
1238 """ 1224 """
1239 Private slot to set the action enabled status. 1225 Private slot to set the action enabled status.
1240 """ 1226 """
1241 enable = bool(self.environmentsComboBox.currentText()) 1227 enable = bool(self.environmentsComboBox.currentText())
1242 enablePip = self.__isPipAvailable() 1228 enablePip = self.__isPipAvailable()
1243 enablePipCache = self.__availablePipVersion() >= (20, 1, 0) 1229 enablePipCache = self.__availablePipVersion() >= (20, 1, 0)
1244 1230
1245 self.__installPipAct.setEnabled(not enablePip) 1231 self.__installPipAct.setEnabled(not enablePip)
1246 self.__installPipUserAct.setEnabled(not enablePip) 1232 self.__installPipUserAct.setEnabled(not enablePip)
1247 self.__repairPipAct.setEnabled(enablePip) 1233 self.__repairPipAct.setEnabled(enablePip)
1248 1234
1249 self.__installPackagesAct.setEnabled(enablePip) 1235 self.__installPackagesAct.setEnabled(enablePip)
1250 self.__installLocalPackageAct.setEnabled(enablePip) 1236 self.__installLocalPackageAct.setEnabled(enablePip)
1251 self.__reinstallPackagesAct.setEnabled(enablePip) 1237 self.__reinstallPackagesAct.setEnabled(enablePip)
1252 1238
1253 self.__installRequirementsAct.setEnabled(enablePip) 1239 self.__installRequirementsAct.setEnabled(enablePip)
1254 self.__uninstallRequirementsAct.setEnabled(enablePip) 1240 self.__uninstallRequirementsAct.setEnabled(enablePip)
1255 self.__generateRequirementsAct.setEnabled(enablePip) 1241 self.__generateRequirementsAct.setEnabled(enablePip)
1256 1242
1257 self.__cacheInfoAct.setEnabled(enablePipCache) 1243 self.__cacheInfoAct.setEnabled(enablePipCache)
1258 self.__cacheShowListAct.setEnabled(enablePipCache) 1244 self.__cacheShowListAct.setEnabled(enablePipCache)
1259 self.__cacheRemoveAct.setEnabled(enablePipCache) 1245 self.__cacheRemoveAct.setEnabled(enablePipCache)
1260 self.__cachePurgeAct.setEnabled(enablePipCache) 1246 self.__cachePurgeAct.setEnabled(enablePipCache)
1261 1247
1262 self.__editVirtualenvConfigAct.setEnabled(enable) 1248 self.__editVirtualenvConfigAct.setEnabled(enable)
1263 1249
1264 self.__checkVulnerabilityAct.setEnabled( 1250 self.__checkVulnerabilityAct.setEnabled(
1265 enable & self.vulnerabilityCheckBox.isEnabled()) 1251 enable & self.vulnerabilityCheckBox.isEnabled()
1266 1252 )
1253
1267 self.__cyclonedxAct.setEnabled(enable) 1254 self.__cyclonedxAct.setEnabled(enable)
1268 1255
1269 self.__showLicensesDialogAct.setEnabled(enable) 1256 self.__showLicensesDialogAct.setEnabled(enable)
1270 1257
1271 @pyqtSlot() 1258 @pyqtSlot()
1272 def __installPip(self): 1259 def __installPip(self):
1273 """ 1260 """
1274 Private slot to install pip into the selected environment. 1261 Private slot to install pip into the selected environment.
1275 """ 1262 """
1276 venvName = self.environmentsComboBox.currentText() 1263 venvName = self.environmentsComboBox.currentText()
1277 if venvName: 1264 if venvName:
1278 self.__pip.installPip(venvName) 1265 self.__pip.installPip(venvName)
1279 self.on_refreshButton_clicked() 1266 self.on_refreshButton_clicked()
1280 1267
1281 @pyqtSlot() 1268 @pyqtSlot()
1282 def __installPipUser(self): 1269 def __installPipUser(self):
1283 """ 1270 """
1284 Private slot to install pip into the user site for the selected 1271 Private slot to install pip into the user site for the selected
1285 environment. 1272 environment.
1286 """ 1273 """
1287 venvName = self.environmentsComboBox.currentText() 1274 venvName = self.environmentsComboBox.currentText()
1288 if venvName: 1275 if venvName:
1289 self.__pip.installPip(venvName, userSite=True) 1276 self.__pip.installPip(venvName, userSite=True)
1290 self.on_refreshButton_clicked() 1277 self.on_refreshButton_clicked()
1291 1278
1292 @pyqtSlot() 1279 @pyqtSlot()
1293 def __repairPip(self): 1280 def __repairPip(self):
1294 """ 1281 """
1295 Private slot to repair the pip installation of the selected 1282 Private slot to repair the pip installation of the selected
1296 environment. 1283 environment.
1297 """ 1284 """
1298 venvName = self.environmentsComboBox.currentText() 1285 venvName = self.environmentsComboBox.currentText()
1299 if venvName: 1286 if venvName:
1300 self.__pip.repairPip(venvName) 1287 self.__pip.repairPip(venvName)
1301 self.on_refreshButton_clicked() 1288 self.on_refreshButton_clicked()
1302 1289
1303 @pyqtSlot() 1290 @pyqtSlot()
1304 def __installPackages(self): 1291 def __installPackages(self):
1305 """ 1292 """
1306 Private slot to install packages to be given by the user. 1293 Private slot to install packages to be given by the user.
1307 """ 1294 """
1308 venvName = self.environmentsComboBox.currentText() 1295 venvName = self.environmentsComboBox.currentText()
1309 if venvName: 1296 if venvName:
1310 from .PipPackagesInputDialog import PipPackagesInputDialog 1297 from .PipPackagesInputDialog import PipPackagesInputDialog
1298
1311 dlg = PipPackagesInputDialog(self, self.tr("Install Packages")) 1299 dlg = PipPackagesInputDialog(self, self.tr("Install Packages"))
1312 if dlg.exec() == QDialog.DialogCode.Accepted: 1300 if dlg.exec() == QDialog.DialogCode.Accepted:
1313 packages, user = dlg.getData() 1301 packages, user = dlg.getData()
1314 self.executeInstallPackages(packages, userSite=user) 1302 self.executeInstallPackages(packages, userSite=user)
1315 1303
1316 @pyqtSlot() 1304 @pyqtSlot()
1317 def __installLocalPackage(self): 1305 def __installLocalPackage(self):
1318 """ 1306 """
1319 Private slot to install a package available on local storage. 1307 Private slot to install a package available on local storage.
1320 """ 1308 """
1321 venvName = self.environmentsComboBox.currentText() 1309 venvName = self.environmentsComboBox.currentText()
1322 if venvName: 1310 if venvName:
1323 from .PipFileSelectionDialog import PipFileSelectionDialog 1311 from .PipFileSelectionDialog import PipFileSelectionDialog
1312
1324 dlg = PipFileSelectionDialog(self, "package") 1313 dlg = PipFileSelectionDialog(self, "package")
1325 if dlg.exec() == QDialog.DialogCode.Accepted: 1314 if dlg.exec() == QDialog.DialogCode.Accepted:
1326 package, user = dlg.getData() 1315 package, user = dlg.getData()
1327 if package and os.path.exists(package): 1316 if package and os.path.exists(package):
1328 self.executeInstallPackages([package], userSite=user) 1317 self.executeInstallPackages([package], userSite=user)
1329 1318
1330 @pyqtSlot() 1319 @pyqtSlot()
1331 def __reinstallPackages(self): 1320 def __reinstallPackages(self):
1332 """ 1321 """
1333 Private slot to force a re-installation of the selected packages. 1322 Private slot to force a re-installation of the selected packages.
1334 """ 1323 """
1335 packages = [itm.text(PipPackagesWidget.PackageColumn) 1324 packages = [
1336 for itm in self.packagesList.selectedItems()] 1325 itm.text(PipPackagesWidget.PackageColumn)
1326 for itm in self.packagesList.selectedItems()
1327 ]
1337 venvName = self.environmentsComboBox.currentText() 1328 venvName = self.environmentsComboBox.currentText()
1338 if venvName and packages: 1329 if venvName and packages:
1339 self.__pip.installPackages(packages, venvName=venvName, 1330 self.__pip.installPackages(packages, venvName=venvName, forceReinstall=True)
1340 forceReinstall=True)
1341 self.on_refreshButton_clicked() 1331 self.on_refreshButton_clicked()
1342 1332
1343 @pyqtSlot() 1333 @pyqtSlot()
1344 def __installRequirements(self): 1334 def __installRequirements(self):
1345 """ 1335 """
1346 Private slot to install packages as given in a requirements file. 1336 Private slot to install packages as given in a requirements file.
1347 """ 1337 """
1348 venvName = self.environmentsComboBox.currentText() 1338 venvName = self.environmentsComboBox.currentText()
1349 if venvName: 1339 if venvName:
1350 self.__pip.installRequirements(venvName) 1340 self.__pip.installRequirements(venvName)
1351 self.on_refreshButton_clicked() 1341 self.on_refreshButton_clicked()
1352 1342
1353 @pyqtSlot() 1343 @pyqtSlot()
1354 def __uninstallRequirements(self): 1344 def __uninstallRequirements(self):
1355 """ 1345 """
1356 Private slot to uninstall packages as given in a requirements file. 1346 Private slot to uninstall packages as given in a requirements file.
1357 """ 1347 """
1358 venvName = self.environmentsComboBox.currentText() 1348 venvName = self.environmentsComboBox.currentText()
1359 if venvName: 1349 if venvName:
1360 self.__pip.uninstallRequirements(venvName) 1350 self.__pip.uninstallRequirements(venvName)
1361 self.on_refreshButton_clicked() 1351 self.on_refreshButton_clicked()
1362 1352
1363 @pyqtSlot() 1353 @pyqtSlot()
1364 def __generateRequirements(self): 1354 def __generateRequirements(self):
1365 """ 1355 """
1366 Private slot to generate the contents for a requirements file. 1356 Private slot to generate the contents for a requirements file.
1367 """ 1357 """
1368 venvName = self.environmentsComboBox.currentText() 1358 venvName = self.environmentsComboBox.currentText()
1369 if venvName: 1359 if venvName:
1370 from .PipFreezeDialog import PipFreezeDialog 1360 from .PipFreezeDialog import PipFreezeDialog
1361
1371 self.__freezeDialog = PipFreezeDialog(self.__pip, self) 1362 self.__freezeDialog = PipFreezeDialog(self.__pip, self)
1372 self.__freezeDialog.show() 1363 self.__freezeDialog.show()
1373 self.__freezeDialog.start(venvName) 1364 self.__freezeDialog.start(venvName)
1374 1365
1375 @pyqtSlot() 1366 @pyqtSlot()
1376 def __editUserConfiguration(self): 1367 def __editUserConfiguration(self):
1377 """ 1368 """
1378 Private slot to edit the user configuration. 1369 Private slot to edit the user configuration.
1379 """ 1370 """
1380 self.__editConfiguration() 1371 self.__editConfiguration()
1381 1372
1382 @pyqtSlot() 1373 @pyqtSlot()
1383 def __editVirtualenvConfiguration(self): 1374 def __editVirtualenvConfiguration(self):
1384 """ 1375 """
1385 Private slot to edit the configuration of the selected environment. 1376 Private slot to edit the configuration of the selected environment.
1386 """ 1377 """
1387 venvName = self.environmentsComboBox.currentText() 1378 venvName = self.environmentsComboBox.currentText()
1388 if venvName: 1379 if venvName:
1389 self.__editConfiguration(venvName=venvName) 1380 self.__editConfiguration(venvName=venvName)
1390 1381
1391 def __editConfiguration(self, venvName=""): 1382 def __editConfiguration(self, venvName=""):
1392 """ 1383 """
1393 Private method to edit a configuration. 1384 Private method to edit a configuration.
1394 1385
1395 @param venvName name of the environment to act upon 1386 @param venvName name of the environment to act upon
1396 @type str 1387 @type str
1397 """ 1388 """
1398 from QScintilla.MiniEditor import MiniEditor 1389 from QScintilla.MiniEditor import MiniEditor
1390
1399 if venvName: 1391 if venvName:
1400 cfgFile = self.__pip.getVirtualenvConfig(venvName) 1392 cfgFile = self.__pip.getVirtualenvConfig(venvName)
1401 if not cfgFile: 1393 if not cfgFile:
1402 return 1394 return
1403 else: 1395 else:
1405 cfgDir = os.path.dirname(cfgFile) 1397 cfgDir = os.path.dirname(cfgFile)
1406 if not cfgDir: 1398 if not cfgDir:
1407 EricMessageBox.critical( 1399 EricMessageBox.critical(
1408 None, 1400 None,
1409 self.tr("Edit Configuration"), 1401 self.tr("Edit Configuration"),
1410 self.tr("""No valid configuration path determined.""" 1402 self.tr("""No valid configuration path determined.""" """ Aborting"""),
1411 """ Aborting""")) 1403 )
1412 return 1404 return
1413 1405
1414 try: 1406 try:
1415 if not os.path.isdir(cfgDir): 1407 if not os.path.isdir(cfgDir):
1416 os.makedirs(cfgDir) 1408 os.makedirs(cfgDir)
1417 except OSError: 1409 except OSError:
1418 EricMessageBox.critical( 1410 EricMessageBox.critical(
1419 None, 1411 None,
1420 self.tr("Edit Configuration"), 1412 self.tr("Edit Configuration"),
1421 self.tr("""No valid configuration path determined.""" 1413 self.tr("""No valid configuration path determined.""" """ Aborting"""),
1422 """ Aborting""")) 1414 )
1423 return 1415 return
1424 1416
1425 if not os.path.exists(cfgFile): 1417 if not os.path.exists(cfgFile):
1426 with contextlib.suppress(OSError), open(cfgFile, "w") as f: 1418 with contextlib.suppress(OSError), open(cfgFile, "w") as f:
1427 f.write("[global]\n") 1419 f.write("[global]\n")
1428 1420
1429 # check, if the destination is writeable 1421 # check, if the destination is writeable
1430 if not os.access(cfgFile, os.W_OK): 1422 if not os.access(cfgFile, os.W_OK):
1431 EricMessageBox.critical( 1423 EricMessageBox.critical(
1432 None, 1424 None,
1433 self.tr("Edit Configuration"), 1425 self.tr("Edit Configuration"),
1434 self.tr("""No valid configuration path determined.""" 1426 self.tr("""No valid configuration path determined.""" """ Aborting"""),
1435 """ Aborting""")) 1427 )
1436 return 1428 return
1437 1429
1438 self.__editor = MiniEditor(cfgFile, "Properties") 1430 self.__editor = MiniEditor(cfgFile, "Properties")
1439 self.__editor.show() 1431 self.__editor.show()
1440 1432
1441 def __pipConfigure(self): 1433 def __pipConfigure(self):
1442 """ 1434 """
1443 Private slot to open the configuration page. 1435 Private slot to open the configuration page.
1444 """ 1436 """
1445 ericApp().getObject("UserInterface").showPreferences("pipPage") 1437 ericApp().getObject("UserInterface").showPreferences("pipPage")
1446 1438
1447 @pyqtSlot() 1439 @pyqtSlot()
1448 def __showCacheInfo(self): 1440 def __showCacheInfo(self):
1449 """ 1441 """
1450 Private slot to show information about the cache. 1442 Private slot to show information about the cache.
1451 """ 1443 """
1452 venvName = self.environmentsComboBox.currentText() 1444 venvName = self.environmentsComboBox.currentText()
1453 if venvName: 1445 if venvName:
1454 self.__pip.showCacheInfo(venvName) 1446 self.__pip.showCacheInfo(venvName)
1455 1447
1456 @pyqtSlot() 1448 @pyqtSlot()
1457 def __showCacheList(self): 1449 def __showCacheList(self):
1458 """ 1450 """
1459 Private slot to show a list of cached files. 1451 Private slot to show a list of cached files.
1460 """ 1452 """
1461 venvName = self.environmentsComboBox.currentText() 1453 venvName = self.environmentsComboBox.currentText()
1462 if venvName: 1454 if venvName:
1463 self.__pip.cacheList(venvName) 1455 self.__pip.cacheList(venvName)
1464 1456
1465 @pyqtSlot() 1457 @pyqtSlot()
1466 def __removeCachedFiles(self): 1458 def __removeCachedFiles(self):
1467 """ 1459 """
1468 Private slot to remove files from the pip cache. 1460 Private slot to remove files from the pip cache.
1469 """ 1461 """
1470 venvName = self.environmentsComboBox.currentText() 1462 venvName = self.environmentsComboBox.currentText()
1471 if venvName: 1463 if venvName:
1472 self.__pip.cacheRemove(venvName) 1464 self.__pip.cacheRemove(venvName)
1473 1465
1474 @pyqtSlot() 1466 @pyqtSlot()
1475 def __purgeCache(self): 1467 def __purgeCache(self):
1476 """ 1468 """
1477 Private slot to empty the pip cache. 1469 Private slot to empty the pip cache.
1478 """ 1470 """
1479 venvName = self.environmentsComboBox.currentText() 1471 venvName = self.environmentsComboBox.currentText()
1480 if venvName: 1472 if venvName:
1481 self.__pip.cachePurge(venvName) 1473 self.__pip.cachePurge(venvName)
1482 1474
1483 ################################################################## 1475 ##################################################################
1484 ## Interface to the vulnerability checks below 1476 ## Interface to the vulnerability checks below
1485 ################################################################## 1477 ##################################################################
1486 1478
1487 @pyqtSlot(bool) 1479 @pyqtSlot(bool)
1488 def on_vulnerabilityCheckBox_clicked(self, checked): 1480 def on_vulnerabilityCheckBox_clicked(self, checked):
1489 """ 1481 """
1490 Private slot handling a change of the automatic vulnerability checks. 1482 Private slot handling a change of the automatic vulnerability checks.
1491 1483
1492 @param checked flag indicating the state of the check box 1484 @param checked flag indicating the state of the check box
1493 @type bool 1485 @type bool
1494 """ 1486 """
1495 if checked: 1487 if checked:
1496 self.__updateVulnerabilityData(clearFirst=True) 1488 self.__updateVulnerabilityData(clearFirst=True)
1497 1489
1498 self.packagesList.header().setSectionHidden( 1490 self.packagesList.header().setSectionHidden(
1499 PipPackagesWidget.VulnerabilityColumn, not checked) 1491 PipPackagesWidget.VulnerabilityColumn, not checked
1500 1492 )
1493
1501 @pyqtSlot() 1494 @pyqtSlot()
1502 def __clearVulnerabilityInfo(self): 1495 def __clearVulnerabilityInfo(self):
1503 """ 1496 """
1504 Private slot to clear the vulnerability info. 1497 Private slot to clear the vulnerability info.
1505 """ 1498 """
1506 for row in range(self.packagesList.topLevelItemCount()): 1499 for row in range(self.packagesList.topLevelItemCount()):
1507 itm = self.packagesList.topLevelItem(row) 1500 itm = self.packagesList.topLevelItem(row)
1508 itm.setText(PipPackagesWidget.VulnerabilityColumn, "") 1501 itm.setText(PipPackagesWidget.VulnerabilityColumn, "")
1509 itm.setToolTip(PipPackagesWidget.VulnerabilityColumn, "") 1502 itm.setToolTip(PipPackagesWidget.VulnerabilityColumn, "")
1510 itm.setIcon(PipPackagesWidget.VulnerabilityColumn, QIcon()) 1503 itm.setIcon(PipPackagesWidget.VulnerabilityColumn, QIcon())
1511 itm.setData(PipPackagesWidget.VulnerabilityColumn, 1504 itm.setData(
1512 PipPackagesWidget.VulnerabilityRole, 1505 PipPackagesWidget.VulnerabilityColumn,
1513 None) 1506 PipPackagesWidget.VulnerabilityRole,
1514 1507 None,
1508 )
1509
1515 @pyqtSlot() 1510 @pyqtSlot()
1516 def __updateVulnerabilityData(self, clearFirst=True): 1511 def __updateVulnerabilityData(self, clearFirst=True):
1517 """ 1512 """
1518 Private slot to update the shown vulnerability info. 1513 Private slot to update the shown vulnerability info.
1519 1514
1520 @param clearFirst flag indicating to clear the vulnerability info first 1515 @param clearFirst flag indicating to clear the vulnerability info first
1521 (defaults to True) 1516 (defaults to True)
1522 @type bool (optional) 1517 @type bool (optional)
1523 """ 1518 """
1524 if clearFirst: 1519 if clearFirst:
1525 self.__clearVulnerabilityInfo() 1520 self.__clearVulnerabilityInfo()
1526 1521
1527 packages = [] 1522 packages = []
1528 for row in range(self.packagesList.topLevelItemCount()): 1523 for row in range(self.packagesList.topLevelItemCount()):
1529 itm = self.packagesList.topLevelItem(row) 1524 itm = self.packagesList.topLevelItem(row)
1530 packages.append(Package( 1525 packages.append(
1531 name=itm.text(PipPackagesWidget.PackageColumn), 1526 Package(
1532 version=itm.text(PipPackagesWidget.InstalledVersionColumn) 1527 name=itm.text(PipPackagesWidget.PackageColumn),
1533 )) 1528 version=itm.text(PipPackagesWidget.InstalledVersionColumn),
1534 1529 )
1535 error, vulnerabilities = ( 1530 )
1536 self.__pip.getVulnerabilityChecker().check(packages) 1531
1537 ) 1532 error, vulnerabilities = self.__pip.getVulnerabilityChecker().check(packages)
1538 if error == VulnerabilityCheckError.OK: 1533 if error == VulnerabilityCheckError.OK:
1539 for package in vulnerabilities: 1534 for package in vulnerabilities:
1540 items = self.packagesList.findItems( 1535 items = self.packagesList.findItems(
1541 package, 1536 package, Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive
1542 Qt.MatchFlag.MatchExactly |
1543 Qt.MatchFlag.MatchCaseSensitive
1544 ) 1537 )
1545 if items: 1538 if items:
1546 itm = items[0] 1539 itm = items[0]
1547 itm.setData( 1540 itm.setData(
1548 PipPackagesWidget.VulnerabilityColumn, 1541 PipPackagesWidget.VulnerabilityColumn,
1549 PipPackagesWidget.VulnerabilityRole, 1542 PipPackagesWidget.VulnerabilityRole,
1550 vulnerabilities[package] 1543 vulnerabilities[package],
1551 ) 1544 )
1552 affected = {v.spec for v in vulnerabilities[package]} 1545 affected = {v.spec for v in vulnerabilities[package]}
1553 itm.setText( 1546 itm.setText(
1554 PipPackagesWidget.VulnerabilityColumn, 1547 PipPackagesWidget.VulnerabilityColumn, ", ".join(affected)
1555 ', '.join(affected)
1556 ) 1548 )
1557 itm.setIcon( 1549 itm.setIcon(
1558 PipPackagesWidget.VulnerabilityColumn, 1550 PipPackagesWidget.VulnerabilityColumn,
1559 UI.PixmapCache.getIcon("securityLow") 1551 UI.PixmapCache.getIcon("securityLow"),
1560 ) 1552 )
1561 1553
1562 elif error in (VulnerabilityCheckError.FullDbUnavailable, 1554 elif error in (
1563 VulnerabilityCheckError.SummaryDbUnavailable): 1555 VulnerabilityCheckError.FullDbUnavailable,
1556 VulnerabilityCheckError.SummaryDbUnavailable,
1557 ):
1564 self.vulnerabilityCheckBox.setChecked(False) 1558 self.vulnerabilityCheckBox.setChecked(False)
1565 self.vulnerabilityCheckBox.setEnabled(False) 1559 self.vulnerabilityCheckBox.setEnabled(False)
1566 self.packagesList.setColumnHidden( 1560 self.packagesList.setColumnHidden(
1567 PipPackagesWidget.VulnerabilityColumn, True) 1561 PipPackagesWidget.VulnerabilityColumn, True
1568 1562 )
1563
1569 @pyqtSlot() 1564 @pyqtSlot()
1570 def __updateVulnerabilityDbCache(self): 1565 def __updateVulnerabilityDbCache(self):
1571 """ 1566 """
1572 Private slot to initiate an update of the local cache of the 1567 Private slot to initiate an update of the local cache of the
1573 vulnerability database. 1568 vulnerability database.
1574 """ 1569 """
1575 with EricOverrideCursor(): 1570 with EricOverrideCursor():
1576 self.__pip.getVulnerabilityChecker().updateVulnerabilityDb() 1571 self.__pip.getVulnerabilityChecker().updateVulnerabilityDb()
1577 1572
1578 def __showVulnerabilityInformation(self, packageName, packageVersion, 1573 def __showVulnerabilityInformation(
1579 vulnerabilities): 1574 self, packageName, packageVersion, vulnerabilities
1575 ):
1580 """ 1576 """
1581 Private method to show the detected vulnerability data. 1577 Private method to show the detected vulnerability data.
1582 1578
1583 @param packageName name of the package 1579 @param packageName name of the package
1584 @type str 1580 @type str
1585 @param packageVersion installed version number 1581 @param packageVersion installed version number
1586 @type str 1582 @type str
1587 @param vulnerabilities list of vulnerabilities 1583 @param vulnerabilities list of vulnerabilities
1588 @type list of Vulnerability 1584 @type list of Vulnerability
1589 """ 1585 """
1590 header = ( 1586 header = self.tr("{0} {1}", "package name, package version").format(
1591 self.tr("{0} {1}", "package name, package version") 1587 packageName, packageVersion
1592 .format(packageName, packageVersion)
1593 ) 1588 )
1594 topItem = QTreeWidgetItem(self.infoWidget, [header]) 1589 topItem = QTreeWidgetItem(self.infoWidget, [header])
1595 topItem.setFirstColumnSpanned(True) 1590 topItem.setFirstColumnSpanned(True)
1596 topItem.setExpanded(True) 1591 topItem.setExpanded(True)
1597 font = topItem.font(0) 1592 font = topItem.font(0)
1598 font.setBold(True) 1593 font.setBold(True)
1599 topItem.setFont(0, font) 1594 topItem.setFont(0, font)
1600 1595
1601 for vulnerability in vulnerabilities: 1596 for vulnerability in vulnerabilities:
1602 title = ( 1597 title = (
1603 vulnerability.cve 1598 vulnerability.cve
1604 if vulnerability.cve else 1599 if vulnerability.cve
1605 vulnerability.vulnerabilityId 1600 else vulnerability.vulnerabilityId
1606 ) 1601 )
1607 titleItem = QTreeWidgetItem(topItem, [title]) 1602 titleItem = QTreeWidgetItem(topItem, [title])
1608 titleItem.setFirstColumnSpanned(True) 1603 titleItem.setFirstColumnSpanned(True)
1609 titleItem.setExpanded(True) 1604 titleItem.setExpanded(True)
1610 1605
1611 QTreeWidgetItem( 1606 QTreeWidgetItem(
1612 titleItem, 1607 titleItem, [self.tr("Affected Version:"), vulnerability.spec]
1613 [self.tr("Affected Version:"), vulnerability.spec]) 1608 )
1614 itm = QTreeWidgetItem( 1609 itm = QTreeWidgetItem(
1615 titleItem, 1610 titleItem, [self.tr("Advisory:"), vulnerability.advisory]
1616 [self.tr("Advisory:"), vulnerability.advisory]) 1611 )
1617 itm.setToolTip(1, "<p>{0}</p>".format( 1612 itm.setToolTip(
1618 vulnerability.advisory.replace("\r\n", "<br/>") 1613 1, "<p>{0}</p>".format(vulnerability.advisory.replace("\r\n", "<br/>"))
1619 )) 1614 )
1620 1615
1621 self.infoWidget.scrollToTop() 1616 self.infoWidget.scrollToTop()
1622 self.infoWidget.resizeColumnToContents(0) 1617 self.infoWidget.resizeColumnToContents(0)
1623 1618
1624 header = self.infoWidget.header() 1619 header = self.infoWidget.header()
1625 header.setStretchLastSection(True) 1620 header.setStretchLastSection(True)
1626 1621
1627 ####################################################################### 1622 #######################################################################
1628 ## Dependency tree related methods below 1623 ## Dependency tree related methods below
1629 ####################################################################### 1624 #######################################################################
1630 1625
1631 @pyqtSlot(bool) 1626 @pyqtSlot(bool)
1632 def on_viewToggleButton_toggled(self, checked): 1627 def on_viewToggleButton_toggled(self, checked):
1633 """ 1628 """
1634 Private slot handling the view selection. 1629 Private slot handling the view selection.
1635 1630
1636 @param checked state of the toggle button 1631 @param checked state of the toggle button
1637 @type bool 1632 @type bool
1638 """ 1633 """
1639 if checked: 1634 if checked:
1640 self.viewsStackWidget.setCurrentWidget( 1635 self.viewsStackWidget.setCurrentWidget(self.dependenciesPage)
1641 self.dependenciesPage)
1642 self.__refreshDependencyTree() 1636 self.__refreshDependencyTree()
1643 else: 1637 else:
1644 self.viewsStackWidget.setCurrentWidget( 1638 self.viewsStackWidget.setCurrentWidget(self.packagesPage)
1645 self.packagesPage)
1646 self.__refreshPackagesList() 1639 self.__refreshPackagesList()
1647 1640
1648 @pyqtSlot(bool) 1641 @pyqtSlot(bool)
1649 def on_requiresButton_toggled(self, checked): 1642 def on_requiresButton_toggled(self, checked):
1650 """ 1643 """
1651 Private slot handling the selection of the view type. 1644 Private slot handling the selection of the view type.
1652 1645
1653 @param checked state of the radio button (unused) 1646 @param checked state of the radio button (unused)
1654 @type bool 1647 @type bool
1655 """ 1648 """
1656 self.__refreshDependencyTree() 1649 self.__refreshDependencyTree()
1657 1650
1658 @pyqtSlot() 1651 @pyqtSlot()
1659 def on_localDepCheckBox_clicked(self): 1652 def on_localDepCheckBox_clicked(self):
1660 """ 1653 """
1661 Private slot handling the switching of the local mode. 1654 Private slot handling the switching of the local mode.
1662 """ 1655 """
1663 self.__refreshDependencyTree() 1656 self.__refreshDependencyTree()
1664 1657
1665 @pyqtSlot() 1658 @pyqtSlot()
1666 def on_userDepCheckBox_clicked(self): 1659 def on_userDepCheckBox_clicked(self):
1667 """ 1660 """
1668 Private slot handling the switching of the 'user-site' mode. 1661 Private slot handling the switching of the 'user-site' mode.
1669 """ 1662 """
1670 self.__refreshDependencyTree() 1663 self.__refreshDependencyTree()
1671 1664
1672 def __refreshDependencyTree(self): 1665 def __refreshDependencyTree(self):
1673 """ 1666 """
1674 Private method to refresh the dependency tree. 1667 Private method to refresh the dependency tree.
1675 """ 1668 """
1676 self.dependenciesList.clear() 1669 self.dependenciesList.clear()
1683 venvName, 1676 venvName,
1684 localPackages=self.localDepCheckBox.isChecked(), 1677 localPackages=self.localDepCheckBox.isChecked(),
1685 usersite=self.userDepCheckBox.isChecked(), 1678 usersite=self.userDepCheckBox.isChecked(),
1686 reverse=self.requiredByButton.isChecked(), 1679 reverse=self.requiredByButton.isChecked(),
1687 ) 1680 )
1688 1681
1689 self.dependenciesList.setUpdatesEnabled(False) 1682 self.dependenciesList.setUpdatesEnabled(False)
1690 for dependency in dependencies: 1683 for dependency in dependencies:
1691 self.__addDependency(dependency, self.dependenciesList) 1684 self.__addDependency(dependency, self.dependenciesList)
1692 1685
1693 self.dependenciesList.sortItems( 1686 self.dependenciesList.sortItems(
1694 PipPackagesWidget.DepPackageColumn, 1687 PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder
1695 Qt.SortOrder.AscendingOrder) 1688 )
1696 for col in range(self.dependenciesList.columnCount()): 1689 for col in range(self.dependenciesList.columnCount()):
1697 self.dependenciesList.resizeColumnToContents(col) 1690 self.dependenciesList.resizeColumnToContents(col)
1698 self.dependenciesList.setUpdatesEnabled(True) 1691 self.dependenciesList.setUpdatesEnabled(True)
1699 1692
1700 self.__updateDepActionButtons() 1693 self.__updateDepActionButtons()
1701 1694
1702 def __addDependency(self, dependency, parent): 1695 def __addDependency(self, dependency, parent):
1703 """ 1696 """
1704 Private method to add a dependency branch to a given parent. 1697 Private method to add a dependency branch to a given parent.
1705 1698
1706 @param dependency dependency to be added 1699 @param dependency dependency to be added
1707 @type dict 1700 @type dict
1708 @param parent reference to the parent item 1701 @param parent reference to the parent item
1709 @type QTreeWidget or QTreeWidgetItem 1702 @type QTreeWidget or QTreeWidgetItem
1710 """ 1703 """
1711 itm = QTreeWidgetItem(parent, [ 1704 itm = QTreeWidgetItem(
1712 dependency["package_name"], 1705 parent,
1713 dependency["installed_version"], 1706 [
1714 dependency["required_version"], 1707 dependency["package_name"],
1715 ]) 1708 dependency["installed_version"],
1709 dependency["required_version"],
1710 ],
1711 )
1716 itm.setExpanded(True) 1712 itm.setExpanded(True)
1717 1713
1718 if dependency["installed_version"] == "?": 1714 if dependency["installed_version"] == "?":
1719 itm.setText(PipPackagesWidget.DepInstalledVersionColumn, 1715 itm.setText(PipPackagesWidget.DepInstalledVersionColumn, self.tr("unknown"))
1720 self.tr("unknown")) 1716
1721
1722 if dependency["required_version"].lower() not in ("any", "?"): 1717 if dependency["required_version"].lower() not in ("any", "?"):
1723 spec = ( 1718 spec = (
1724 "=={0}".format(dependency["required_version"]) 1719 "=={0}".format(dependency["required_version"])
1725 if dependency["required_version"][0] in "0123456789" else 1720 if dependency["required_version"][0] in "0123456789"
1726 dependency["required_version"] 1721 else dependency["required_version"]
1727 ) 1722 )
1728 specifierSet = SpecifierSet(specifiers=spec) 1723 specifierSet = SpecifierSet(specifiers=spec)
1729 if not specifierSet.contains(dependency["installed_version"]): 1724 if not specifierSet.contains(dependency["installed_version"]):
1730 itm.setIcon(PipPackagesWidget.DepRequiredVersionColumn, 1725 itm.setIcon(
1731 UI.PixmapCache.getIcon("warning")) 1726 PipPackagesWidget.DepRequiredVersionColumn,
1732 1727 UI.PixmapCache.getIcon("warning"),
1728 )
1729
1733 elif dependency["required_version"].lower() == "any": 1730 elif dependency["required_version"].lower() == "any":
1734 itm.setText(PipPackagesWidget.DepRequiredVersionColumn, 1731 itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("any"))
1735 self.tr("any")) 1732
1736
1737 elif dependency["required_version"] == "?": 1733 elif dependency["required_version"] == "?":
1738 itm.setText(PipPackagesWidget.DepRequiredVersionColumn, 1734 itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("unknown"))
1739 self.tr("unknown")) 1735
1740
1741 # recursively add sub-dependencies 1736 # recursively add sub-dependencies
1742 for dep in dependency["dependencies"]: 1737 for dep in dependency["dependencies"]:
1743 self.__addDependency(dep, itm) 1738 self.__addDependency(dep, itm)
1744 1739
1745 @pyqtSlot(QTreeWidgetItem, int) 1740 @pyqtSlot(QTreeWidgetItem, int)
1746 def on_dependenciesList_itemActivated(self, item, column): 1741 def on_dependenciesList_itemActivated(self, item, column):
1747 """ 1742 """
1748 Private slot reacting on a package item of the dependency tree being 1743 Private slot reacting on a package item of the dependency tree being
1749 activated. 1744 activated.
1750 1745
1751 @param item reference to the activated item 1746 @param item reference to the activated item
1752 @type QTreeWidgetItem 1747 @type QTreeWidgetItem
1753 @param column activated column 1748 @param column activated column
1754 @type int 1749 @type int
1755 """ 1750 """
1756 packageName = item.text(PipPackagesWidget.DepPackageColumn) 1751 packageName = item.text(PipPackagesWidget.DepPackageColumn)
1757 packageVersion = item.text( 1752 packageVersion = item.text(PipPackagesWidget.DepInstalledVersionColumn)
1758 PipPackagesWidget.DepInstalledVersionColumn) 1753
1759
1760 self.__showPackageDetails(packageName, packageVersion) 1754 self.__showPackageDetails(packageName, packageVersion)
1761 1755
1762 @pyqtSlot() 1756 @pyqtSlot()
1763 def on_dependenciesList_itemSelectionChanged(self): 1757 def on_dependenciesList_itemSelectionChanged(self):
1764 """ 1758 """
1765 Private slot reacting on a change of selected items of the dependency 1759 Private slot reacting on a change of selected items of the dependency
1766 tree. 1760 tree.
1767 """ 1761 """
1768 if len(self.dependenciesList.selectedItems()) == 0: 1762 if len(self.dependenciesList.selectedItems()) == 0:
1769 self.dependencyInfoWidget.clear() 1763 self.dependencyInfoWidget.clear()
1770 1764
1771 @pyqtSlot(QTreeWidgetItem, int) 1765 @pyqtSlot(QTreeWidgetItem, int)
1772 def on_dependenciesList_itemPressed(self, item, column): 1766 def on_dependenciesList_itemPressed(self, item, column):
1773 """ 1767 """
1774 Private slot reacting on a package item of the dependency tree being 1768 Private slot reacting on a package item of the dependency tree being
1775 pressed. 1769 pressed.
1776 1770
1777 @param item reference to the pressed item 1771 @param item reference to the pressed item
1778 @type QTreeWidgetItem 1772 @type QTreeWidgetItem
1779 @param column pressed column 1773 @param column pressed column
1780 @type int 1774 @type int
1781 """ 1775 """
1782 self.dependencyInfoWidget.clear() 1776 self.dependencyInfoWidget.clear()
1783 1777
1784 if item is not None: 1778 if item is not None:
1785 self.__showPackageInformation( 1779 self.__showPackageInformation(
1786 item.text(PipPackagesWidget.DepPackageColumn), 1780 item.text(PipPackagesWidget.DepPackageColumn), self.dependencyInfoWidget
1787 self.dependencyInfoWidget 1781 )
1788 ) 1782
1789
1790 self.__updateDepActionButtons() 1783 self.__updateDepActionButtons()
1791 1784
1792 @pyqtSlot() 1785 @pyqtSlot()
1793 def on_refreshDependenciesButton_clicked(self): 1786 def on_refreshDependenciesButton_clicked(self):
1794 """ 1787 """
1795 Private slot to refresh the dependency tree. 1788 Private slot to refresh the dependency tree.
1796 """ 1789 """
1797 currentEnvironment = self.environmentsComboBox.currentText() 1790 currentEnvironment = self.environmentsComboBox.currentText()
1798 self.environmentsComboBox.clear() 1791 self.environmentsComboBox.clear()
1799 self.dependenciesList.clear() 1792 self.dependenciesList.clear()
1800 1793
1801 with EricOverrideCursor(): 1794 with EricOverrideCursor():
1802 self.__populateEnvironments() 1795 self.__populateEnvironments()
1803 1796
1804 index = self.environmentsComboBox.findText( 1797 index = self.environmentsComboBox.findText(
1805 currentEnvironment, 1798 currentEnvironment,
1806 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive 1799 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
1807 ) 1800 )
1808 if index != -1: 1801 if index != -1:
1809 self.environmentsComboBox.setCurrentIndex(index) 1802 self.environmentsComboBox.setCurrentIndex(index)
1810 1803
1811 self.__updateDepActionButtons() 1804 self.__updateDepActionButtons()
1812 1805
1813 @pyqtSlot() 1806 @pyqtSlot()
1814 def on_showDepPackageDetailsButton_clicked(self): 1807 def on_showDepPackageDetailsButton_clicked(self):
1815 """ 1808 """
1816 Private slot to show information for the selected package of the 1809 Private slot to show information for the selected package of the
1817 dependency tree. 1810 dependency tree.
1818 """ 1811 """
1819 item = self.dependenciesList.selectedItems()[0] 1812 item = self.dependenciesList.selectedItems()[0]
1820 if item: 1813 if item:
1821 packageName = item.text(PipPackagesWidget.DepPackageColumn) 1814 packageName = item.text(PipPackagesWidget.DepPackageColumn)
1822 packageVersion = item.text( 1815 packageVersion = item.text(PipPackagesWidget.DepInstalledVersionColumn)
1823 PipPackagesWidget.DepInstalledVersionColumn) 1816
1824
1825 self.__showPackageDetails(packageName, packageVersion) 1817 self.__showPackageDetails(packageName, packageVersion)
1826 1818
1827 def __updateDepActionButtons(self): 1819 def __updateDepActionButtons(self):
1828 """ 1820 """
1829 Private method to set the state of the dependency page action buttons. 1821 Private method to set the state of the dependency page action buttons.
1830 """ 1822 """
1831 self.showDepPackageDetailsButton.setEnabled( 1823 self.showDepPackageDetailsButton.setEnabled(
1832 len(self.dependenciesList.selectedItems()) == 1 and 1824 len(self.dependenciesList.selectedItems()) == 1 and self.__isPipAvailable()
1833 self.__isPipAvailable() 1825 )
1834 ) 1826
1835
1836 ################################################################## 1827 ##################################################################
1837 ## Interface to show the licenses dialog below 1828 ## Interface to show the licenses dialog below
1838 ################################################################## 1829 ##################################################################
1839 1830
1840 @pyqtSlot() 1831 @pyqtSlot()
1841 def __showLicensesDialog(self): 1832 def __showLicensesDialog(self):
1842 """ 1833 """
1843 Private slot to show a dialog with the licenses of the selected 1834 Private slot to show a dialog with the licenses of the selected
1844 environment. 1835 environment.
1845 """ 1836 """
1846 from .PipLicensesDialog import PipLicensesDialog 1837 from .PipLicensesDialog import PipLicensesDialog
1847 1838
1848 environment = self.environmentsComboBox.currentText() 1839 environment = self.environmentsComboBox.currentText()
1849 localPackages = ( 1840 localPackages = (
1850 self.localDepCheckBox.isChecked() 1841 self.localDepCheckBox.isChecked()
1851 if self.viewToggleButton.isChecked() else 1842 if self.viewToggleButton.isChecked()
1852 self.localCheckBox.isChecked() 1843 else self.localCheckBox.isChecked()
1853 ) 1844 )
1854 usersite = ( 1845 usersite = (
1855 self.userDepCheckBox.isChecked() 1846 self.userDepCheckBox.isChecked()
1856 if self.viewToggleButton.isChecked() else 1847 if self.viewToggleButton.isChecked()
1857 self.userCheckBox.isChecked() 1848 else self.userCheckBox.isChecked()
1858 ) 1849 )
1859 dlg = PipLicensesDialog( 1850 dlg = PipLicensesDialog(
1860 self.__pip, 1851 self.__pip,
1861 environment, 1852 environment,
1862 localPackages=localPackages, 1853 localPackages=localPackages,
1863 usersite=usersite, 1854 usersite=usersite,
1864 parent=self 1855 parent=self,
1865 ) 1856 )
1866 dlg.exec() 1857 dlg.exec()
1867 1858
1868 ################################################################## 1859 ##################################################################
1869 ## Interface to create a SBOM file using CycloneDX 1860 ## Interface to create a SBOM file using CycloneDX
1870 ################################################################## 1861 ##################################################################
1871 1862
1872 @pyqtSlot() 1863 @pyqtSlot()
1873 def __createSBOMFile(self): 1864 def __createSBOMFile(self):
1874 """ 1865 """
1875 Private slot to create a "Software Bill Of Material" file. 1866 Private slot to create a "Software Bill Of Material" file.
1876 """ 1867 """
1877 import CycloneDXInterface 1868 import CycloneDXInterface
1878 1869
1879 venvName = self.environmentsComboBox.currentText() 1870 venvName = self.environmentsComboBox.currentText()
1880 if venvName == self.__pip.getProjectEnvironmentString(): 1871 if venvName == self.__pip.getProjectEnvironmentString():
1881 venvName = "<project>" 1872 venvName = "<project>"
1882 CycloneDXInterface.createCycloneDXFile(venvName) 1873 CycloneDXInterface.createCycloneDXFile(venvName)

eric ide

mercurial