Fri, 04 Nov 2022 11:55:21 +0100
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
9453 | 1 | # -*- coding: utf-8 -*- |
2 | ||
3 | # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> | |
4 | # | |
5 | ||
6 | """ | |
7 | Module implementing a dialog showing the isort code formatting progress and the results. | |
8 | """ | |
9 | ||
10 | import copy | |
11 | import io | |
12 | import multiprocessing | |
13 | import pathlib | |
14 | ||
15 | from dataclasses import dataclass | |
16 | ||
9468
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
17 | from isort import settings |
9453 | 18 | from isort.api import check_file, sort_file |
19 | ||
20 | from PyQt6.QtCore import pyqtSlot, Qt, QCoreApplication | |
21 | from PyQt6.QtWidgets import ( | |
22 | QAbstractButton, | |
23 | QDialog, | |
24 | QDialogButtonBox, | |
25 | QHeaderView, | |
26 | QTreeWidgetItem, | |
27 | ) | |
28 | ||
29 | from eric7 import Preferences | |
30 | from eric7.EricWidgets import EricMessageBox | |
31 | ||
32 | from .FormattingDiffWidget import FormattingDiffWidget | |
33 | from .IsortFormattingAction import IsortFormattingAction | |
34 | from .IsortUtilities import suppressStderr | |
35 | ||
36 | from .Ui_IsortFormattingDialog import Ui_IsortFormattingDialog | |
37 | ||
38 | ||
39 | class IsortFormattingDialog(QDialog, Ui_IsortFormattingDialog): | |
40 | """ | |
41 | Class implementing a dialog showing the isort code formatting progress and the | |
42 | results. | |
43 | """ | |
44 | ||
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
45 | DataRole = Qt.ItemDataRole.UserRole |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
46 | DataTypeRole = Qt.ItemDataRole.UserRole + 1 |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
47 | FileNameRole = Qt.ItemDataRole.UserRole + 2 |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
48 | StatusRole = Qt.ItemDataRole.UserRole + 3 |
9453 | 49 | |
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
50 | FileNameColumn = 1 |
9453 | 51 | StatusColumn = 0 |
52 | ||
53 | def __init__( | |
54 | self, | |
55 | configuration, | |
56 | filesList, | |
57 | project=None, | |
58 | action=IsortFormattingAction.Sort, | |
59 | parent=None, | |
60 | ): | |
61 | """ | |
62 | Constructor | |
63 | ||
64 | @param configuration dictionary containing the configuration parameters | |
65 | @type dict | |
66 | @param filesList list of absolute file paths to be processed | |
67 | @type list of str | |
68 | @param project reference to the project object (defaults to None) | |
69 | @type Project (optional) | |
70 | @param action action to be performed (defaults to IsortFormattingAction.Sort) | |
71 | @type IsortFormattingAction (optional) | |
72 | @param parent reference to the parent widget (defaults to None) | |
73 | @type QWidget (optional) | |
74 | """ | |
75 | super().__init__(parent) | |
76 | self.setupUi(self) | |
77 | ||
78 | self.progressBar.setMaximum(len(filesList)) | |
79 | ||
80 | self.resultsList.header().setSortIndicator(1, Qt.SortOrder.AscendingOrder) | |
81 | ||
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
82 | self.__project = project |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
83 | |
9453 | 84 | self.__config = copy.deepcopy(configuration) |
85 | self.__config["quiet"] = True # we don't want extra output | |
86 | self.__config["overwrite_in_place"] = True # we want to overwrite the files | |
87 | if "config_source" in self.__config: | |
88 | del self.__config["config_source"] | |
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
89 | |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
90 | # Create an isort Config object and pre-load it with parameters contained in |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
91 | # project specific configuration files (like pyproject.toml). The configuration |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
92 | # given as a dictionary (i.e. data entered in the configuration dialog) |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
93 | # overwrites these. If the project is not passed, the isort config is based on |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
94 | # the isort defaults. |
9468
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
95 | if project: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
96 | # clear the caches in order to force a re-read of config files |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
97 | settings._get_config_data.cache_clear() |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
98 | settings._find_config.cache_clear() |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
99 | try: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
100 | self.__isortConfig = ( |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
101 | settings.Config(settings_path=project.getProjectPath(), **self.__config) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
102 | if project |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
103 | else settings.Config(**self.__config) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
104 | ) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
105 | except KeyError: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
106 | # invalid configuration entry found in some config file; use just the dialog |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
107 | # parameters |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
108 | self.__isortConfig = settings.Config(**self.__config) |
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
109 | |
9453 | 110 | self.__config["__action__"] = action # needed by the workers |
111 | ||
112 | self.__filesList = filesList[:] | |
113 | ||
114 | self.__diffDialog = None | |
115 | ||
116 | self.__allFilter = self.tr("<all>") | |
117 | ||
118 | self.__sortImportsButton = self.buttonBox.addButton( | |
119 | self.tr("Sort Imports"), QDialogButtonBox.ButtonRole.ActionRole | |
120 | ) | |
121 | self.__sortImportsButton.setVisible(False) | |
122 | ||
123 | self.show() | |
124 | QCoreApplication.processEvents() | |
125 | ||
126 | self.__performAction() | |
127 | ||
128 | def __performAction(self): | |
129 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
130 | Private method to execute the requested sorting action. |
9453 | 131 | """ |
132 | self.progressBar.setValue(0) | |
133 | self.progressBar.setVisible(True) | |
134 | ||
135 | self.statisticsGroup.setVisible(False) | |
136 | self.__statistics = IsortStatistics() | |
137 | ||
138 | self.__cancelled = False | |
139 | ||
140 | self.statusFilterComboBox.clear() | |
141 | self.resultsList.clear() | |
142 | ||
143 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) | |
144 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) | |
145 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) | |
146 | ||
147 | files = self.__filterFiles(self.__filesList) | |
148 | if len(files) > 1: | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
149 | self.__sortManyFiles(files) |
9453 | 150 | elif len(files) == 1: |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
151 | self.__sortOneFile(files[0]) |
9453 | 152 | |
153 | def __filterFiles(self, filesList): | |
154 | """ | |
155 | Private method to filter the given list of files according the | |
156 | configuration parameters. | |
157 | ||
158 | @param filesList list of files | |
159 | @type list of str | |
160 | @return list of filtered files | |
161 | @rtype list of str | |
162 | """ | |
163 | files = [] | |
164 | for file in filesList: | |
165 | if not self.__isortConfig.is_supported_filetype( | |
166 | file | |
167 | ) or self.__isortConfig.is_skipped(pathlib.Path(file)): | |
168 | self.__handleIsortResult(file, "skipped") | |
169 | else: | |
170 | files.append(file) | |
171 | ||
172 | return files | |
173 | ||
174 | def __resort(self): | |
175 | """ | |
176 | Private method to resort the result list. | |
177 | """ | |
178 | self.resultsList.sortItems( | |
179 | self.resultsList.sortColumn(), | |
180 | self.resultsList.header().sortIndicatorOrder(), | |
181 | ) | |
182 | ||
183 | def __resizeColumns(self): | |
184 | """ | |
185 | Private method to resize the columns of the result list. | |
186 | """ | |
187 | self.resultsList.header().resizeSections( | |
188 | QHeaderView.ResizeMode.ResizeToContents | |
189 | ) | |
190 | self.resultsList.header().setStretchLastSection(True) | |
191 | ||
192 | def __populateStatusFilterCombo(self): | |
193 | """ | |
194 | Private method to populate the status filter combo box with allowed selections. | |
195 | """ | |
196 | allowedSelections = set() | |
197 | for row in range(self.resultsList.topLevelItemCount()): | |
198 | allowedSelections.add( | |
199 | self.resultsList.topLevelItem(row).text( | |
200 | IsortFormattingDialog.StatusColumn | |
201 | ) | |
202 | ) | |
203 | ||
204 | self.statusFilterComboBox.addItem(self.__allFilter) | |
205 | self.statusFilterComboBox.addItems(sorted(allowedSelections)) | |
206 | ||
207 | def __finish(self): | |
208 | """ | |
209 | Private method to perform some actions after the run was performed or canceled. | |
210 | """ | |
211 | self.__resort() | |
212 | self.__resizeColumns() | |
213 | ||
214 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) | |
215 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) | |
216 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) | |
217 | ||
218 | self.progressBar.setVisible(False) | |
219 | ||
220 | self.__sortImportsButton.setVisible( | |
221 | self.__config["__action__"] is not IsortFormattingAction.Sort | |
222 | and self.__statistics.changeCount > 0 | |
223 | ) | |
224 | ||
225 | self.__updateStatistics() | |
226 | self.__populateStatusFilterCombo() | |
227 | ||
228 | def __updateStatistics(self): | |
229 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
230 | Private method to update the statistics about the recent sorting run and |
9453 | 231 | make them visible. |
232 | """ | |
233 | self.reformattedLabel.setText( | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
234 | self.tr("Resorted:") |
9453 | 235 | if self.__config["__action__"] is IsortFormattingAction.Sort |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
236 | else self.tr("Would Resort:") |
9453 | 237 | ) |
238 | ||
239 | total = self.progressBar.maximum() | |
240 | ||
241 | self.totalCountLabel.setText("{0:n}".format(total)) | |
242 | self.skippedCountLabel.setText("{0:n}".format(self.__statistics.skippedCount)) | |
243 | self.failuresCountLabel.setText("{0:n}".format(self.__statistics.failureCount)) | |
244 | self.processedCountLabel.setText( | |
245 | "{0:n}".format(self.__statistics.processedCount) | |
246 | ) | |
247 | self.reformattedCountLabel.setText( | |
248 | "{0:n}".format(self.__statistics.changeCount) | |
249 | ) | |
250 | self.unchangedCountLabel.setText("{0:n}".format(self.__statistics.sameCount)) | |
251 | ||
252 | self.statisticsGroup.setVisible(True) | |
253 | ||
254 | @pyqtSlot(QAbstractButton) | |
255 | def on_buttonBox_clicked(self, button): | |
256 | """ | |
257 | Private slot to handle button presses of the dialog buttons. | |
258 | ||
259 | @param button reference to the pressed button | |
260 | @type QAbstractButton | |
261 | """ | |
262 | if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): | |
263 | self.__cancelled = True | |
264 | elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): | |
265 | self.accept() | |
266 | elif button is self.__sortImportsButton: | |
267 | self.__sortImportsButtonClicked() | |
268 | ||
269 | @pyqtSlot() | |
270 | def __sortImportsButtonClicked(self): | |
271 | """ | |
272 | Private slot handling the selection of the 'Sort Imports' button. | |
273 | """ | |
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
274 | files = [] |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
275 | for row in range(self.resultsList.topLevelItemCount()): |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
276 | itm = self.resultsList.topLevelItem(row) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
277 | if itm.data(0, IsortFormattingDialog.StatusRole) == "changed": |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
278 | files.append(itm.data(0, IsortFormattingDialog.FileNameRole)) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
279 | if files: |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
280 | self.__filesList = files |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
281 | |
9453 | 282 | self.__config["__action__"] = IsortFormattingAction.Sort |
283 | self.__performAction() | |
284 | ||
285 | @pyqtSlot(QTreeWidgetItem, int) | |
286 | def on_resultsList_itemDoubleClicked(self, item, column): | |
287 | """ | |
288 | Private slot handling a double click of a result item. | |
289 | ||
290 | @param item reference to the double clicked item | |
291 | @type QTreeWidgetItem | |
292 | @param column column number that was double clicked | |
293 | @type int | |
294 | """ | |
295 | dataType = item.data(0, IsortFormattingDialog.DataTypeRole) | |
296 | if dataType == "error": | |
297 | EricMessageBox.critical( | |
298 | self, | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
299 | self.tr("Imports Sorting Failure"), |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
300 | self.tr( |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
301 | "<p>Imports sorting failed due to this error.</p><p>{0}</p>" |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
302 | ).format(item.data(0, IsortFormattingDialog.DataRole)), |
9453 | 303 | ) |
304 | elif dataType == "diff": | |
305 | if self.__diffDialog is None: | |
306 | self.__diffDialog = FormattingDiffWidget() | |
307 | self.__diffDialog.showDiff(item.data(0, IsortFormattingDialog.DataRole)) | |
308 | ||
309 | @pyqtSlot(str) | |
310 | def on_statusFilterComboBox_currentTextChanged(self, status): | |
311 | """ | |
312 | Private slot handling the selection of a status for items to be shown. | |
313 | ||
314 | @param status selected status | |
315 | @type str | |
316 | """ | |
317 | for row in range(self.resultsList.topLevelItemCount()): | |
318 | itm = self.resultsList.topLevelItem(row) | |
319 | itm.setHidden( | |
320 | status != self.__allFilter | |
321 | and itm.text(IsortFormattingDialog.StatusColumn) != status | |
322 | ) | |
323 | ||
324 | def closeEvent(self, evt): | |
325 | """ | |
326 | Protected slot implementing a close event handler. | |
327 | ||
328 | @param evt reference to the close event | |
329 | @type QCloseEvent | |
330 | """ | |
331 | if self.__diffDialog is not None: | |
332 | self.__diffDialog.close() | |
333 | evt.accept() | |
334 | ||
335 | def __handleIsortResult(self, filename, status, data=""): | |
336 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
337 | Private method to handle an isort sorting result. |
9453 | 338 | |
339 | @param filename name of the processed file | |
340 | @type str | |
341 | @param status status of the performed action (one of 'changed', 'failed', | |
342 | 'skipped' or 'unchanged') | |
343 | @type str | |
344 | @param data action data (error message or unified diff) (defaults to "") | |
345 | @type str (optional) | |
346 | """ | |
347 | isError = False | |
348 | ||
349 | if status == "changed": | |
350 | statusMsg = ( | |
351 | self.tr("would resort") | |
352 | if self.__config["__action__"] | |
353 | in (IsortFormattingAction.Check, IsortFormattingAction.Diff) | |
354 | else self.tr("resorted") | |
355 | ) | |
356 | self.__statistics.changeCount += 1 | |
357 | ||
358 | elif status == "unchanged": | |
359 | statusMsg = self.tr("unchanged") | |
360 | self.__statistics.sameCount += 1 | |
361 | ||
362 | elif status == "skipped": | |
363 | statusMsg = self.tr("skipped") | |
364 | self.__statistics.skippedCount += 1 | |
365 | ||
366 | elif status == "failed": | |
367 | statusMsg = self.tr("failed") | |
368 | self.__statistics.failureCount += 1 | |
369 | isError = True | |
370 | ||
371 | elif status == "unsupported": | |
372 | statusMsg = self.tr("error") | |
373 | data = self.tr("Unsupported 'isort' action ({0}) given.").format( | |
374 | self.__config["__action__"] | |
375 | ) | |
376 | self.__statistics.failureCount += 1 | |
377 | isError = True | |
378 | ||
379 | else: | |
380 | statusMsg = self.tr("invalid status ({0})").format(status) | |
381 | self.__statistics.failureCount += 1 | |
382 | isError = True | |
383 | ||
384 | if status != "skipped": | |
385 | self.__statistics.processedCount += 1 | |
386 | ||
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
387 | itm = QTreeWidgetItem( |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
388 | self.resultsList, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
389 | [ |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
390 | statusMsg, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
391 | self.__project.getRelativePath(filename) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
392 | if self.__project |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
393 | else filename, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
394 | ], |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
395 | ) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
396 | itm.setData(0, IsortFormattingDialog.StatusRole, status) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
397 | itm.setData(0, IsortFormattingDialog.FileNameRole, filename) |
9453 | 398 | if data: |
399 | itm.setData( | |
400 | 0, IsortFormattingDialog.DataTypeRole, "error" if isError else "diff" | |
401 | ) | |
402 | itm.setData(0, IsortFormattingDialog.DataRole, data) | |
403 | ||
404 | self.progressBar.setValue(self.progressBar.value() + 1) | |
405 | ||
406 | QCoreApplication.processEvents() | |
407 | ||
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
408 | def __sortManyFiles(self, files): |
9453 | 409 | """ |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
410 | Private method to sort imports of the list of files according the configuration |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
411 | using multiple processes in parallel. |
9453 | 412 | |
413 | @param files list of files to be processed | |
414 | @type list of str | |
415 | """ | |
416 | maxProcesses = Preferences.getUI("BackgroundServiceProcesses") | |
417 | if maxProcesses == 0: | |
418 | # determine based on CPU count | |
419 | try: | |
420 | NumberOfProcesses = multiprocessing.cpu_count() | |
421 | if NumberOfProcesses >= 1: | |
422 | NumberOfProcesses -= 1 | |
423 | except NotImplementedError: | |
424 | NumberOfProcesses = 1 | |
425 | else: | |
426 | NumberOfProcesses = maxProcesses | |
427 | ||
428 | # Create queues | |
429 | taskQueue = multiprocessing.Queue() | |
430 | doneQueue = multiprocessing.Queue() | |
431 | ||
432 | # Submit tasks (initially two times the number of processes) | |
433 | tasks = len(files) | |
434 | initialTasks = min(2 * NumberOfProcesses, tasks) | |
435 | for _ in range(initialTasks): | |
436 | file = files.pop(0) | |
437 | taskQueue.put((file, self.__config["__action__"])) | |
438 | ||
439 | # Start worker processes | |
440 | workers = [ | |
441 | multiprocessing.Process( | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
442 | target=self.sortingWorkerTask, |
9453 | 443 | args=(taskQueue, doneQueue, self.__isortConfig), |
444 | ) | |
445 | for _ in range(NumberOfProcesses) | |
446 | ] | |
447 | for worker in workers: | |
448 | worker.start() | |
449 | ||
450 | # Get the results from the worker tasks | |
451 | for _ in range(tasks): | |
452 | result = doneQueue.get() | |
453 | self.__handleIsortResult(result.filename, result.status, data=result.data) | |
454 | ||
455 | if self.__cancelled: | |
456 | break | |
457 | ||
458 | if files: | |
459 | file = files.pop(0) | |
460 | taskQueue.put((file, self.__config["__action__"])) | |
461 | ||
462 | # Tell child processes to stop | |
463 | for _ in range(NumberOfProcesses): | |
464 | taskQueue.put("STOP") | |
465 | ||
466 | for worker in workers: | |
467 | worker.join() | |
468 | worker.close() | |
469 | ||
470 | taskQueue.close() | |
471 | doneQueue.close() | |
472 | ||
473 | self.__finish() | |
474 | ||
475 | @staticmethod | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
476 | def sortingWorkerTask(inputQueue, outputQueue, isortConfig): |
9453 | 477 | """ |
478 | Static method acting as the parallel worker for the formatting task. | |
479 | ||
480 | @param inputQueue input queue | |
481 | @type multiprocessing.Queue | |
482 | @param outputQueue output queue | |
483 | @type multiprocessing.Queue | |
484 | @param isortConfig config object for isort | |
485 | @type isort.Config | |
486 | """ | |
487 | for file, action in iter(inputQueue.get, "STOP"): | |
488 | if action == IsortFormattingAction.Diff: | |
489 | result = IsortFormattingDialog.__isortCheckFile( | |
490 | file, | |
491 | isortConfig, | |
492 | withDiff=True, | |
493 | ) | |
494 | ||
495 | elif action == IsortFormattingAction.Sort: | |
496 | result = IsortFormattingDialog.__isortSortFile( | |
497 | file, | |
498 | isortConfig, | |
499 | ) | |
500 | ||
501 | else: | |
502 | result = IsortResult( | |
503 | status="unsupported", | |
504 | filename=file, | |
505 | ) | |
506 | ||
507 | outputQueue.put(result) | |
508 | ||
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
509 | def __sortOneFile(self, file): |
9453 | 510 | """ |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
511 | Private method to sort the imports of the list of files according the |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
512 | configuration. |
9453 | 513 | |
514 | @param file name of the file to be processed | |
515 | @type str | |
516 | """ | |
517 | if self.__config["__action__"] == IsortFormattingAction.Diff: | |
518 | result = IsortFormattingDialog.__isortCheckFile( | |
519 | file, | |
520 | self.__isortConfig, | |
521 | withDiff=True, | |
522 | ) | |
523 | ||
524 | elif self.__config["__action__"] == IsortFormattingAction.Sort: | |
525 | result = IsortFormattingDialog.__isortSortFile( | |
526 | file, | |
527 | self.__isortConfig, | |
528 | ) | |
529 | ||
530 | else: | |
531 | result = IsortResult( | |
532 | status="unsupported", | |
533 | filename=file, | |
534 | ) | |
535 | ||
536 | self.__handleIsortResult(result.filename, result.status, data=result.data) | |
537 | ||
538 | self.__finish() | |
539 | ||
540 | @staticmethod | |
541 | def __isortCheckFile(filename, isortConfig, withDiff=True): | |
542 | """ | |
543 | Static method to check, if a file's import statements need to be changed. | |
544 | ||
545 | @param filename name of the file to be processed | |
546 | @type str | |
547 | @param isortConfig config object for isort | |
548 | @type isort.Config | |
549 | @param withDiff flag indicating to return a unified diff, if the file needs to | |
550 | be changed (defaults to True) | |
551 | @type bool (optional) | |
552 | @return result object | |
553 | @rtype IsortResult | |
554 | """ | |
555 | diffIO = io.StringIO() if withDiff else False | |
556 | with suppressStderr(): | |
557 | ok = check_file(filename, show_diff=diffIO, config=isortConfig) | |
558 | if withDiff: | |
559 | data = "" if ok else diffIO.getvalue() | |
560 | diffIO.close() | |
561 | else: | |
562 | data = "" | |
563 | ||
564 | status = "unchanged" if ok else "changed" | |
565 | ||
566 | return IsortResult(status=status, filename=filename, data=data) | |
567 | ||
568 | @staticmethod | |
569 | def __isortSortFile(filename, isortConfig): | |
570 | """ | |
571 | Static method to sort the import statements of a file. | |
572 | ||
573 | @param filename name of the file to be processed | |
574 | @type str | |
575 | @param isortConfig config object for isort | |
576 | @type isort.Config | |
577 | @return result object | |
578 | @rtype IsortResult | |
579 | """ | |
580 | with suppressStderr(): | |
581 | ok = sort_file( | |
582 | filename, | |
583 | config=isortConfig, | |
584 | ask_to_apply=False, | |
585 | write_to_stdout=False, | |
586 | show_diff=False, | |
587 | ) | |
588 | ||
589 | status = "changed" if ok else "unchanged" | |
590 | ||
591 | return IsortResult(status=status, filename=filename) | |
592 | ||
593 | ||
594 | @dataclass | |
595 | class IsortStatistics: | |
596 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
597 | Class containing the isort statistic data. |
9453 | 598 | """ |
599 | ||
600 | skippedCount: int = 0 | |
601 | changeCount: int = 0 | |
602 | sameCount: int = 0 | |
603 | failureCount: int = 0 | |
604 | processedCount: int = 0 | |
605 | ||
606 | ||
607 | @dataclass | |
608 | class IsortResult: | |
609 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
610 | Class containing the isort result data. |
9453 | 611 | """ |
612 | ||
613 | status: str = "" | |
614 | filename: str = "" | |
615 | data: str = "" |